When a relay's model is not recognized by address, the metadata lookup
falls back to the vendor's entry: first the entry that same-name entries
point at via canonical_model_id, and only if none resolves, an entry
listed under a provider that some canonical id points at.
That last step only checked whether the provider counts as a vendor, not
whether the entry claims to be its own. models.dev has no
deepseek/deepseek-v4.1-flash-1017, so the ~40 entries pointing there all
miss, and thinkingmachines (counted as a vendor for its own models) won
with its LoRA fine-tuning entry DeepSeek-V4.1-Flash-1017:peft:262144,
whose canonical id points back at deepseek. It is text-only with a 262144
window, so picking the model filled every app with text-only input and a
quarter of the real window; Codex additionally turned image input off.
Skip entries whose canonical id names another vendor in that fallback.
Against the current models.dev data, 27 ids stop borrowing from hosts of
other vendors' models (thinkingmachines, NVIDIA, vivgrid, alibaba) and
leave the fields empty instead, and laguna-xs-2.1 now resolves to
poolside's own entry rather than being ambiguous. Skipping ':' variants
instead was rejected: it fixes this id but newly resolves nine
thinkingmachines entries carrying 65K training windows (gpt-oss-120b and
others).
Refs #8765, #8682
getPresetLookbackStart subtracted (N-1) * 24h from the current instant
and then rounded to local midnight. Across a DST offset change that
subtraction can land on the adjacent date, so the 7d/14d/30d presets
included one extra local day after spring-forward and dropped one after
fall-back (e.g. America/New_York, Europe/Berlin). The shifted range fed
the usage summary, model/provider stats and request-log queries.
Build the start date from today's local year/month/day minus (N-1) days
instead, which yields the first instant of that calendar day, including
zones where midnight itself is skipped by DST. The 1d rolling window,
today, all-time and custom ranges are unchanged.
Refs #8719
models.dev moved OpenCode Zen and Go to /inference/... endpoints, while
presets and existing providers still use /zen/... (both work and bill
the same). The endpoint lookup no longer found them, so models only
listed there, such as deepseek-v4.1-flash, filled nothing. Map the old
normalized addresses to the new ones; this applies to every app's fill.
The 272K GPT cap (v4.0.13) applied to every window the metadata lookup
returned, so platforms that really serve the 1.05M OpenAI API window
(OpenRouter and other providers models.dev recognizes by address) were
capped too. The risk the cap guards against is narrower: a relay that
models.dev does not know falls back to the vendor's own entry, and its
1.05M lands on a model that is often served from a ChatGPT subscription
with a 272K window.
- Same-endpoint presets and the models.dev provider matched by address
keep their own windows; only the window borrowed from the vendor entry
(including when the matched provider lists none) is capped at 272K.
- A window the user types is kept as is. When a GPT model's window is
above 272K, the Codex, Hermes, OpenClaw, OpenCode and Pi forms show a
note under the field: relay GPT usually has the subscription window, and
an oversized one delays compaction until the upstream rejects the turn.
Text added in zh / en / ja / zh-TW.
Existing rows are not migrated. Codex GPT rows saved before v4.0.13 with
an auto-filled 1.05M now take effect since #8385; the note shows when the
form is opened.
Skill discovery, install, check-updates and update all download the
whole GitHub repo ZIP. The compressed body was collected into a Vec in
memory and capped at 128 MiB, so repos whose docs/ images dwarf the
skills themselves (K-Dense-AI/scientific-agent-skills: 235 MiB ZIP,
277 MiB uncompressed, 20 MiB of skills) failed before extraction even
though they fit the 512 MiB extraction budget and 30k entry cap. The
download cap also reused ARCHIVE_TOO_LARGE, so the UI blamed the
extraction limit.
Stream the body chunk by chunk into an anonymous temp file (deleted on
drop, including error paths), still counting actually received bytes,
and open the ZipArchive from that file. With memory no longer the
constraint, align the download cap with the extraction budget, since a
larger compressed body could not be extracted anyway. Report the
download cap under its own ARCHIVE_DOWNLOAD_TOO_LARGE code with
four-locale text. Extraction budget, entry cap and path/symlink
protections are unchanged.
Refs #8421
models.dev lists the OpenAI API window for GPT models (1.05M for the
GPT-5.6 and GPT-6 families), but many relays serve GPT through ChatGPT
subscriptions, whose Codex backend catalogs these models at 272K. The
base URL cannot tell the two apart. An overstated window keeps clients
from compacting until the upstream rejects the prompt and the turn
fails, while an understated one only compacts earlier.
- Cap every GPT window resolveModelMetadata returns at 272K, whether it
comes from a preset, the recognized models.dev provider or the vendor
fallback; smaller windows such as gpt-4o's are left alone
- Lower the GPT windows in the Hermes, OpenClaw, OpenCode, Pi and Codex
presets to the same cap, with a test guarding every preset source
- Pin the Claude Code ChatGPT subscription preset's context and
auto-compact windows to 272K; the 372K it used was the backend's
mid-2026 value and now sits above the backend's limit
MCP, prompt and Pi native prompt editors open as non-modal full pages (SheetPageContent) inside the content area, so the sidebar and window drag region stay usable. Provider editors drop the 1008px cap and follow the window width like the list pages.
Full-page editors register unsaved changes (input, change, and clicks on checkbox / switch / radio / option / aria-pressed controls; display-only toggles opt out with data-unsaved-ignore). Leaving through the sidebar, Cmd+, or the tray asks before discarding; back, cancel and Escape still close directly. Editor saves keep navigation locked via mutation keys.
Also truncates long page titles, makes the whole page header a drag region, and removes the editor delete buttons that no caller used.
The in-app updater only follows stable releases and its manifest notes
just say "Release vX", so users jumping between stable versions never
saw what the versions in between changed. The full release notes are
too long for a popup.
- Each version gets src/whats-new/<version>.json: up to 4 one-line
items (new / fix / improve) in zh, zh-TW, en and ja, bundled into the
app at build time. "items": [] marks a version that should not pop up.
- On the first launch after an update, a dialog lists every non-empty
version between the last one seen and the current one, newest first.
Older versions beyond the first three are collapsed. Each version
links to its page in the website changelog.
- The last seen version is stored per device in settings.json
(whatsNewSeenVersion) and only moves forward, so a downgrade and
re-upgrade does not repeat anything. Without a record, only the
current version is shown. When there is nothing to show, the version
is recorded silently so the next update has a correct starting point.
- The dialog waits for the welcome and new-layout dialogs. Confirming
either of them also records the current version, so a 3.x user does
not get two dialogs in a row.
- Settings > About gets a "Recent updates" button to open the summaries
again. The 4.0.0 summary is included so that list starts from 4.0; no
update popup can reach it, since only 4.0.2 and later record a version.
- The release workflow first requires the tagged version's summary file
and validates every bundled summary with the unit test, since a
malformed item would crash the dialog and put the whole app on the
error screen.
With every cached image in use, a newly loaded entry (refs 0) was the only
evictable one and got revoked before its waiting hook acquired it, leaving
the image stuck on the loading placeholder; two loads finishing together
could also evict each other. Count pending waiters per image and skip
images that still have one when evicting.
Rebuild the session page around structured transcripts.
- Session list: project groups start collapsed and remember the ones
expanded by hand; unchanged session files reuse their parsed metadata.
- Backend: all nine agents' transcripts parse into structured blocks
(text, thinking, tool call / result, image, event, step); the Codex
rollout parser is rewritten. New commands stream transcripts over a
Channel, serve full block content, image bytes and Finder reveal behind
validated content refs, and export a session as a Markdown file; a
transcript cache (8 entries / 96 MB) keyed by source fingerprints.
open_external now only allows http, https and mailto.
- Reader: turns laid out as prompt -> execution steps -> final reply, with
steps folded into a one-line summary, failures always shown, each
agent's native symbols and verbs, all / conversation / changes filters,
a conversation outline, in-session find, Markdown rendering (ported
from #6332), image lightbox and copy / export as Markdown.
- Review follow-ups: clippy, Codex 0.119-0.128 duplicate steps and late
failure status, Grok Build in-progress refresh, full arguments for long
shell commands, and shared block helpers across the parsers.
Co-authored-by: bigben446 <bigben446@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
Unify the visual and interaction rules on top of the v7 redesign.
- Theme color #F97315 for --action-* and --ring; dark mode uses #EA580C
with white text, toned-down mode colors and translucent soft fills
- Buttons mapped to three sizes (28 / 32 / 36); inputs, selects and
search fields share fieldClass; self-drawn checkbox and radio
- HoverTip replaces title on about 100 icon buttons; it stays closed
when a closing menu returns focus to its trigger
- Install tailwindcss-animate so menu, popover, dialog and tooltip
enter/exit animations take effect; sliding segment thumb and tab
underline
- Remove the command palette and the header prompt entry; provider card
primary actions become icon buttons
- Virtualize the session list and the Skills discovery list; optimistic
toggles for MCP and Skills with a 300ms delayed disabled state
- Toasts drawn unstyled with status-colored icons and de-duplicated
- Windows: hide the system title bar and use in-app window controls;
macOS: brand row under the traffic lights
- Focus rings only for keyboard users
- Skills discovery and the prompt list show the last result from a
local cache and refresh in the background
- Drop the trailing ellipsis from 36 dialog-opening labels
- Provider list fills the main area
Co-authored-by: 千羽 <qyinter@users.noreply.github.com>
Give each visual signal one meaning across pages.
Modes: only the mode being viewed gets the raised fill; the active mode
keeps its colour and dot. The Stack preview starts Stack from a row
("Start Stack with this as default…"), like the routing preview, so the
dialog no longer picks a default. The mode help moves next to the tabs,
the routing status drops the local address, the two-step explanation and
the "advanced feature" checkbox leave the dialogs, and added cards in
additive apps are white; grey now only marks the default provider.
Navigation: page-level switches use underline tabs (new PageTabs, also
used by Usage), with a smaller second level for OpenClaw config, Hermes
memory and Skills. Segmented controls stay for modes and in-page filters.
Hermes memory keeps unsaved edits when switching files.
Danger: irreversible confirmations use the red button; recoverable ones
(back to direct, remove from app, uninstall Skills, rebuild Codex usage,
delete env vars) no longer use red.
Pages:
- MCP/Skills matrix: one square checkbox shape for every state, and the
column header highlights and shows the app name on hover or focus.
- Skills: drop the update banner; "N updatable" in the header filters
the table and offers "Update all"; the unmanaged banner keeps only
Ignore; the installed count lives on the tab only.
- Add provider: "All" is split by category, names sorted within each;
versions split into plan and region, shown as two segmented rows when
they form a grid and as a dropdown otherwise.
- Usage: the trend chart shows the selected metric only; today's log
rows show the time only and the provider column gets the space.
- Sessions: the reader merges into the page header.
- Accounts: rows say how many providers use them, and removing an
account confirms first and names the affected providers.
- Apps: small neutral switches for "Show"; Settings says "Check for new
versions of your apps at launch".
Also add the provider empty state, a one-time "new layout" notice for
existing users, and a 1040px max width for card lists on wide windows.
Presets: add optional family/versionKey fields so one vendor's regional
and plan variants show as a single row in step 1 (Claude 94 presets ->
71 rows) and as a version switch in step 2. Switching versions after the
user typed into the form asks first, since the form is refilled. The
category field and preset order are unchanged.
Tray: show a dot and a problem tooltip on the icon only while the
routing service failed to start, fell back to direct, or a tray action
failed; spell out that quitting stops the routing service while it is in
use; name the default provider in the Stack submenu title; add the
"restart X to apply" line after tray switches; refresh the tray when the
routing service starts or stops.
Panels: record when the background session scan last finished and show
the newer of that and a manual sync on the usage page; add
resync_mcp_to_apps and resync_skills_to_apps; report per-repo failures
from skill discovery and update checks; report ZIP skills skipped
because the directory is taken. MCP/Skills bulk actions keep applying
to the filtered rows only.
Foundation for the v7 redesign; no layout changes yet.
- Add the v7 tokens (surfaces, text, action, inverse, direct / route / stack,
status, chart, shadows) to index.css and expose them in Tailwind together
with v7 radius, shadow and type-role scales; teach tailwind-merge the new
names so text-body no longer cancels text-fg-1.
- Align the shadcn HSL tokens (background, card, muted, border, ring) with the
v7 palette and use the neutral ring for focus outlines.
- Rename light / dark to Light / Dark Side of the Moon with System first, and
pause transitions while switching so colors don't fade in piece by piece.
- Add v7 button variants (solid, neutral, quiet, direct, route, stack) and
sizes, plus HelpTip / DisabledReason (the "?" card), SegmentedControl,
Notice / NoticeSlot and Sheet.
- Respect the OS reduced-motion setting for framer-motion.
A vendor that also hosts other vendors' models (NVIDIA lists
minimaxai/minimax-m2.7 without a canonical id) was counted as a second
vendor next to the entry relays point at, so the fallback gave up and
models like MiniMax-M2.7, gpt-5.4 and kimi-k2.6 got no metadata through
unrecognized relays. Use the entries relays point at first and only fall
back to a vendor's own entry when nothing points anywhere.
Without a thinking map, Pi shows off..high for a custom model and hides
xhigh and max, even when the upstream supports them. When a fetched
model is picked and has no map yet, fill one:
- Use the reviewed map of the Pi preset at the same endpoint, but only
under the same API and when the compat it relies on does not conflict
with what the user wrote. Missing compat keys are added to the model,
and for openai-completions the thinking format and reasoning_effort
support that Pi detects must match the preset's.
- Otherwise, when Pi sends levels verbatim as reasoning_effort
(openai-responses, or openai-completions detected as the OpenAI
thinking format), derive the map from models.dev effort levels using
Pi's own getEffortThinkingLevelMap rule.
A model with its own baseUrl is now looked up by that address. Update
the Pi design docs accordingly.
A provider's /models endpoint only returns model IDs, so a model picked
from the fetched list left its context window, output limit, reasoning
levels, and modalities empty, while the same model in a preset had them.
When a model is picked in the Codex, OpenClaw, Hermes, OpenCode, or
MiniMax Code form, look up its metadata field by field:
1. the preset model with the same endpoint (hand-verified values);
2. the models.dev provider whose API endpoint matches;
3. the vendor's own models.dev entry, without its price.
Endpoints match only when host and path are equal after dropping a
trailing version segment or request path, so plans that share a host
(z.ai standard vs Coding Plan, OpenCode Zen vs Go) never borrow each
other's values. Models are never matched by name across providers: the
same model ID often has very different context windows per deployment.
Only empty fields are filled, and reasoning or image input is only ever
turned on. models.dev is prefetched once the model list is fetched and
shared with the usage pricing import; when it arrives late the row is
filled again, located by its stable row key. A single toast asks the
user to review what was filled.
Frontend half of the logging overhaul.
Capture:
- Add a global error boundary plus window error/unhandledrejection
handlers that persist renderer errors via tauri-plugin-log (new
@tauri-apps/plugin-log dependency + log:default capability), so a
white-screen crash leaves a trace.
Redaction (two layers over every logged value):
- Structured serializer: redact by sensitive property name (arrays and
nested values included) and by opaque value shape.
- Text layer, the universal final pass: redact URL credentials/query,
auth schemes, known secret shapes, and named-key array/object values.
The container rule closes prefix+JSON, double-encoded, and POJO-error
bypasses regardless of which entrypoint the structured data came from.
- Render Errors as redacted message + native stack across V8 and WebKit
(macOS/Linux) without depending on a single stack format.
- Bound all inputs and drop oversized JSON to keep the UI responsive.
i18n:
- zh / en / ja / zh-TW for the new log settings and error text.
Usage/quota queries frequently showed spurious "query failed" states that
manual refresh could not clear (#3820). Root cause: all transport-level
failures were folded into Ok(success:false), so react-query's retry never
fired and the failure body poisoned the cache as regular data.
Backend:
- balance/coding_plan/subscription services now return Err for send
failures and body-read failures (read body via bytes() before
serde_json::from_slice; reqwest's json() wraps read errors as Decode,
making error-kind checks on it dead code). Auth/4xx/parse errors stay
Ok(success:false) and surface immediately.
- Script path maps transient AppError keys (request_failed /
read_response_failed) to Err; Volcengine adds a Transient call variant.
- Command layer skips snapshot persistence, usage-cache-updated emit and
tray refresh on Err so the cache bridge cannot overwrite retained data.
- Expired-credential retry propagates transient errors instead of
rewriting them as "OAuth token has expired".
Frontend:
- resolveDisplayUsage generalized to subscription quotas; 5th param is now
an options object with a `rejected` flag: stale success data retained by
react-query across rejections is re-anchored to dataUpdatedAt and expires
through the same 10-minute keep-last-good window instead of being shown
indefinitely (a total outage used to mask longer than a single 5xx).
- useSubscriptionQuota / useCodexOauthQuota gain keep-last-good with
per-scope snapshot reset; rejected queries with no displayable value
synthesize a failure placeholder carrying the real error message so
footers keep rendering a retry entry point.
- HTTP 429 is classified transient (retry-later) alongside 5xx.
- UsageScriptModal surfaces string rejections via extractErrorMessage.
Tests: 6 backend behavior tests drive real HTTP against a local listener
to pin the Err/Ok channel semantics; frontend suite extends
keepLastGoodUsage coverage (405 passing).
- useUsageQuery: retry once + keep-last-good — show the last successful
result for up to 10min when a query fails transiently (network/timeout/
HTTP 5xx), so a single blip no longer flips the card to red. Deterministic
failures (auth, empty key, unknown provider, 4xx) surface immediately and
clear the snapshot so a stale quota can't resurface after credentials change.
- bump native balance/coding-plan/subscription request timeouts 10s -> 15s
for slow cross-border endpoints.
- coding_plan: return explicit errors ("API key is empty" / "Unknown coding
plan provider") instead of a blank failure, mirroring balance.
- add unit tests for keep-last-good and transient/deterministic classification.
Polish the provider-level User-Agent override UI on the Claude and Codex forms.
- Add a shared CustomUserAgentField (label + input + preset dropdown + live
validation) so both forms stay in sync.
- Provide curated UA presets (Claude Code / Kilo Code families that pass
coding-plan UA whitelists per #3671); the first is Claude Code's real
`claude-cli/x (external, cli)` format. Whitelists gate on the name prefix,
not the version, so static values stay valid across upgrades.
- Expose presets via a dropdown to the right of the input (z-[200] so it
renders above the dialog layers) instead of inline chips.
- Move the field into the existing advanced/reasoning collapsibles.
- userAgent.ts mirrors the backend byte rule (reject only control chars;
non-ASCII is allowed) for a non-blocking inline hint.
- i18n for all four locales (zh/en/ja/zh-TW).