# Unreleased

# v1.1.9 (2026-08-23)

## Fixes
- **Antigravity Gemini 3.7 retains its proven model-aware fingerprint while keeping 9router-compatible thinking normalization.** A live post-release smoke test showed that Google's IDE profile returns `404` for Gemini 3.7 even though it works for Opus and other Antigravity models. Gemini 3.7 therefore uses the AGY CLI fingerprint that exposes it, quota discovery uses the same profile, and all other Antigravity chat models remain on the static official IDE fingerprint; unsupported `xhigh`, `max`, and `auto` thinking values are still normalized before reaching Google.

# v1.1.8 (2026-08-23)

## Fixes
- **Antigravity Gemini 3.7 now matches 9router's validated IDE transport and thinking levels.** Chat and quota requests consistently use the static Antigravity IDE fingerprint instead of switching Gemini 3.7 to the CLI fingerprint, while Gemini 3 reasoning values are normalized to the supported `minimal`, `low`, `medium`, or `high` enum so `xhigh`, `max`, and `auto` can no longer reach Google as invalid values. The normalization remains scoped to Antigravity and leaves standalone Gemini CLI and every other provider unchanged.

# v1.1.7 (2026-08-22)

## Fixes
- **Published CLI bundles now exclude repository worktrees at every packaging boundary.** Next standalone tracing ignores `.worktrees`, the bundle copier drops any traced worktree directory, and the npm payload audit rejects one if it reappears, preventing unrelated checkout artifacts from entering a release.

# v1.1.6 (2026-08-22)

## Fixes
- **Antigravity Gemini 3.7 requests now use the fingerprint that exposes the model without regressing Opus.** Gemini 3.7 model IDs are routed through the validated AGY CLI user agent, while Opus and every other Antigravity model retain the official IDE fingerprint required by their working transport. The selector is deliberately model-scoped so prefixed lookalikes and future version numbers cannot silently switch clients.

# v1.1.5 (2026-08-22)

## Fixes
- **Antigravity Opus tool streams now match the working official IDE transport.** Chat requests use the validated daily Cloud Code host, one official envelope and header fingerprint, numeric client sessions, bounded official request identities, and the 64K output ceiling required by large thinking/tool workloads. Successful streams preserve a truthful `tool_calls` or `stop` terminal followed by `[DONE]` instead of closing early or surfacing an invalid event stream.
- **Rate-limited Antigravity accounts rotate immediately without wasting the request budget.** A 429 is returned untouched to the outer auth layer on the first upstream attempt, preserving quota-reset metadata while avoiding same-credential credit retries, `Retry-After` sleeps, and exponential backoff before a healthy account is selected.

# v1.1.4 (2026-08-22)

## Fixes
- **Slow Antigravity thinking requests no longer die on the generic 15-second response-acquisition deadline.** Thinking routes now use a 60-second connect floor that can widen from successful p95 latency without shrinking below the safety budget. A true pre-response timeout skips futile same-credential replay and spends the existing three-attempt cap on distinct eligible accounts without poisoning account health; aborting protected-host transports also closes sockets without leaking `uncaughtException: AbortError` into the server process.

# v1.1.3 (2026-08-22)

## Fixes
- **A sick Antigravity account can no longer exhaust failover before a healthy account is reached.** Attempts consumed by confirmed account auth, policy, quota, or rate-limit failures are replaced before credential rotation, while provider-wide, transport, and protocol failures retain the global three-attempt safety bound. Google's explicit organization-level Gemini Code Assist restriction is classified before refresh, skips a futile token replay, quarantines the affected account across Antigravity models, and immediately advances to the next connection; unrelated 403 responses keep the existing bounded refresh path.

# v1.1.2 (2026-08-22)

## Fixes
- **Claude-backed Antigravity tool declarations now satisfy the upstream custom-tool grammar.** Names containing dots, colons, slashes, spaces, or sanitization collisions are allocated deterministic unique identifiers from the shared Gemini/Claude-safe subset, while streamed calls and tool history continue to restore the exact client-declared names.
- **Antigravity now preserves every OpenAI tool-choice mode end to end.** `required` and `none` reach the upstream function-calling configuration instead of being overwritten with `VALIDATED`; named choices remain deterministic even though the Claude-backed endpoint ignores `allowedFunctionNames`, by exposing only the selected declaration for that request.

# v1.1.1 (2026-08-22)

## Fixes
- **Validated Kiro API-key streams may finish at their native metering terminal.** Kiro can emit semantic response events followed by `contextUsageEvent` and `meteringEvent`, then close cleanly without a separate `messageStopEvent`; the integrity decoder previously rejected that valid sequence as an invalid event stream. A metering terminal is now accepted only after semantic output and only as the final verified frame, while CRC corruption, truncation, metering-only responses, and semantic events after metering remain failures.
- **Adaptive latency learning can no longer shorten approved stream safety budgets.** Successful-stream percentiles previously reduced Antigravity thinking idle timeouts to roughly 63 seconds despite the 120-second static budget, while the slower timed-out requests never entered the learning set. Learned first-event and progress-idle deadlines now only widen their provider/model floors; explicit environment overrides remain authoritative and learned expansion stays capped at 900 seconds.
- **Local candidate snapshots now wait for the retiring runtime and preserve private filesystem modes.** The rollback installer waits for the captured LaunchAgent PID to exit before copying live data and reapplies internal file/directory permissions before integrity verification, preventing macOS copy races or `0700` directories becoming `0755` without weakening snapshot validation.

# v1.1.0 (2026-08-22)

## Compatibility
- **The v1.1 provider registry now validates canonical descriptors before exposing compatibility views.** Provider identity, aliases, auth modes, formats, endpoints, model IDs, quota sources, refresh/deadline/integrity profiles, media capabilities, and dashboard metadata are checked centrally; Antigravity, Kiro, Codex, Cursor, and GitHub retain their existing public aliases and model coverage.
- **Cross-format requests preserve the v1.1 fields their target APIs understand.** OpenAI Chat service tiers survive Responses conversion, Responses-only client metadata is stripped before Chat forwarding, and Claude adaptive thinking keeps its adaptive mode plus effort without fabricating a legacy token budget.
- **Codex model discovery now uses the installed ZevaiRouter version and a refresh-safe authenticated retry.** Catalog requests identify ZevaiRouter, include the active account when available, persist a rotated credential before one bounded retry, expand review models, and fall back to the static catalog with sanitized diagnostics on malformed or unavailable live data.
- **Cursor gains live model discovery and a capability-gated AgentService transport.** Plain non-streaming text turns may use the current HTTP/2 AgentService protocol, while streams, tools, images, prefill, explicit output limits, proxies, and any unsupported shape retain the proven legacy path; malformed or committed responses cannot be replayed unsafely.

## Security
- **Outbound URLs now pass one DNS-pinned trust policy.** Public fetches reject private, reserved, mixed-resolution, credential-bearing, and redirected targets; authenticated LAN providers require explicit host/CIDR and port trust, while unchanged legacy private nodes remain usable without authorizing new origins.
- **API-key restrictions now match admission and discovery.** OpenAI and Gemini model lists, model metadata, Gemini generation paths, chat admission, and connection selection consume the same immutable model/connection policy snapshot without leaking disallowed catalog entries.

## Reliability
- **Quota progress now comes from one generation-safe server coordinator.** Concurrent dashboard subscribers share one bounded refresh, receive cached snapshots plus ordered account updates/errors/completion, preserve stale successes, reject late writes from older generations, and reconnect without launching per-account browser request storms.
- **Dashboard data consumers now share a small stale-while-revalidate resource cache.** Provider, model, alias, CLI, combo, endpoint, and usage views deduplicate concurrent requests, abort only after the final subscriber leaves, protect newer generations from late responses, retry failures, and invalidate exact mutation keys.
- **The Usage routing topology now renders sanitized live lifecycle events.** A bounded replayable SSE ring drives admitted, attempting, streaming, failover, terminal, degraded, and probe states; bursts batch into animation frames, completed paths expire, and reduced-motion users receive equivalent static status instead of synthetic traffic.
- **Disabled optional services now stay absent from startup.** Core quota and token loops wait for database readiness, while Tunnel, Tailscale, MITM, Headroom, and auto-ping load only from enabled branches after an unref'd three-second delay; settings changes reconfigure only affected service state, and shutdown is idempotent. PXPIPE remains request-lazy and loads its transform only when explicitly enabled.
- **Provider token refreshes now run through isolated provider lanes.** Kiro refreshes serialize with a three-second minimum start interval, other providers retain bounded parallelism, queued caller aborts are removed without cancelling shared upstream work, and a slow provider cannot block unrelated refreshes.
- **Kiro streams now validate AWS EventStream integrity before trusting frames.** Message bounds, typed headers, prelude/message CRCs, exception frames, truncation, and required terminal metadata are checked incrementally; corrupt scopes enter a bounded verified-spool mode with one safe retry and five-probe recovery.
- **Every streamed request now has one truthful terminal and one lifecycle result.** Premature EOF, transport resets, timeouts, translator failures, and provider protocol errors emit a client-format failure terminal instead of fabricating `finish_reason: "stop"` or closing silently; success, usage, account consequences, and request logs finalize at most once.
- **Adaptive candidate admission is deterministic without weakening route constraints.** Immutable secret-free snapshots apply capability, API-key, connection-pin, quota, transport, and scoped-circuit exclusions before a weighted health/priority/quota/latency/stability score; stable legacy order and connection ID resolve ties.
- **Provider, egress, and protocol-integrity failures now open independent bounded circuits.** Cross-account/provider evidence is required for shared scopes, half-open probes admit one request, two successful probes recover the route, failed probes back off up to five minutes, and client aborts or policy rejection never count as provider evidence.
- **Every upstream executor call now belongs to one monotonic attempt lease under a shared three-attempt request budget.** Same-account retry, refreshed-credential replay, and account failover consume the same budget; semantic commit forbids replay, while usage, health, circuit, probe, timeout, abort, and terminal races finalize at most once.
- **Stream deadlines now adapt by phase instead of using one fixed stall window.** Generic routes use bounded `15s/60s/60s/300s` connect/first-event/progress-idle/absolute budgets and Antigravity thinking uses `15s/240s/120s/600s`; after twenty successful observations, first-event and idle budgets learn from p95/p99 while validated environment overrides remain authoritative.
- **Adaptive routing ships shadow-first with secret-safe telemetry.** Shadow mode compares the deterministic winner while executing the legacy account, active selection requires both the global flag and a persisted provider activation, explicit pins remain hard constraints, and a bounded lifecycle-event ring plus Prometheus counters expose decisions without credentials, prompts, responses, emails, cookies, or raw upstream errors. Active routing remains disabled by default pending local and canary acceptance.

## Improvements
- **Warm Precision stays visually intact while dashboard work mounts and decodes lazily.** Closed provider, connection, model-select, edit, and Kiro dialogs no longer keep forms, hooks, or catalogue fetches alive; non-critical provider imagery uses native lazy loading/async decoding, while modal focus, responsive sizing, and the approved routing-map language remain unchanged. This unreleased candidate still requires local owner acceptance before any version bump, push, publish, or VPS rollout.
- **Provider connection actions now use one consistent entry point.** Every connection-capable provider exposes one `Add Connection` action, while `Open Automation` remains adjacent for automation hubs. Multi-auth providers open a compact method chooser; CodeBuddy groups OAuth, API Key, and Quota Cookie there, while single-auth providers still open their form directly. Kiro retains its specialized picker. Empty and populated provider states now share the same action component, and narrow mobile rows stack cleanly.
- **Mobile dashboard scrolling now has a production touch regression.** The verifier exercises long Provider and Usage pages, compact dynamic viewport height, and chooser-to-OAuth handoff cleanup. Shell or modal behavior is changed only where the browser reproduction identifies a failing boundary.
- **Mobile navigation now scrolls to every dashboard destination.** The drawer is bounded to the dynamic viewport and its navigation owns overflow, so touch users can reach System and Settings on short screens. The desktop dashboard canvas also uses the approved Neutral Warm treatment: a faint coral atmosphere with neutral grid lines instead of an orange cast.

# v1.0.271 (2026-08-22)

## Fixes
- **Authenticated Kiro API keys without a CodeWhisperer profile no longer appear disconnected.** Headless Kiro keys can answer `ListAvailableProfiles` with HTTP 200 and `profiles: []`, then successfully run chat and quota requests without a `profileArn`; the provider Check and saved-connection Test paths incorrectly treated the missing optional profile as an invalid credential. Both paths now use successful completion of the authenticated profile-list probe as the validity signal while 401/403, network failures, and upstream errors remain failures. The Test path is covered through the real connection-testing flow rather than source-text assertions.

# v1.0.270 (2026-08-21)

## Fixes
- **Slow Antigravity thinking prefill no longer creates false 60-second stream failures or locks healthy accounts.** The raw upstream watchdog now distinguishes the wait for the first byte from a later inter-byte stall: Antigravity thinking models receive a bounded 240-second prefill window and a 120-second active-stream window, while other routes keep the 60-second defaults. Both phases emit precise timeout metadata, reset only on real raw bytes, remain configurable with validated environment variables, and are treated as transport-only failures after the response starts instead of triggering account cooldown or fallback. Responses clients receive the exact timeout phase in `response.failed`; conversely, a valid protocol terminal now stops the watchdog and cancels an upstream socket that incorrectly stays open, preventing a completed request from being relabelled as stalled.

# v1.0.269 (2026-08-21)

## Fixes
- **The Kiro Add Connection popup now fits its content.** The authentication picker and simple forms use a compact desktop width, while an active Bulk Account monitor still expands for its browser preview, account status, and event log; mobile keeps the existing full-width sheet.

# v1.0.268 (2026-08-21)

## Fixes
- **Provider creation no longer appears twice.** The redundant toolbar `Add Provider` menu was removed; the explicit OpenAI-compatible and Anthropic-compatible actions remain in the Custom Providers section.

# v1.0.267 (2026-08-21)

## Fixes
- **The Quota Tracker provider menu now stays above account cards.** The toolbar's backdrop blur and the cards' transforms created separate stacking contexts, so only the first dropdown row remained visible/clickable while lower provider options were covered by quota content. The toolbar now elevates only while the menu is open, preserving the existing layout and filtering behavior on desktop and mobile.

# v1.0.266 (2026-08-21)

## Improvements
- **The dashboard now feels consistent without changing ZevaiRouter's established design or routing behavior.** Shared controls, feedback, empty states, and layout-matched skeletons now give Endpoint, Providers, Usage, and Quota the same hierarchy and responsive rhythm. Endpoint copy/key actions are clearer, Providers gains unified search/filter/add controls and accessible mobile actions, Usage retains valid statistics during REST/SSE interruptions, and Quota keeps valid account data visible during refresh failures while switching dense comparisons to labelled mobile rows. Focus visibility, reduced-motion handling, toast semantics, modal sizing, and narrow-screen overflow were tightened across the shell; backend APIs, provider behavior, Usage calculations, and the Routing Map remain unchanged.

## Fixes
- **Reverse keyboard navigation can no longer escape the open mobile sidebar.** The drawer initially focuses its dialog surface for announcement, but `Shift+Tab` previously moved to the overlay behind it; focus now wraps to the drawer's last control and Escape still restores focus to the menu trigger.

# v1.0.265 (2026-08-21)

## Improvements
- **The Usage routing map now reacts as one coordinated live-traffic system instead of lighting only the cable.** Active requests energize the ZevaiRouter core, send a layered cyan/lime beam with directional particles toward the selected provider, and trigger a provider-colored lift/glow on arrival. Error routes use a slower red pulse, the last completed route settles into a quiet amber afterglow, idle nodes stay still, unusual provider IDs cannot collide in SVG filters, and reduced-motion mode suppresses turbulence, particles, transforms and repeating effects while preserving static status cues.
- **The Endpoint page no longer duplicates the Token Saver workspace.** Removed the obsolete Token Saver promotion card and its dead link/import because the workspace already has a dedicated dashboard navigation entry; the Endpoint layout now proceeds directly to API Keys.

# v1.0.264 (2026-08-21)

## Fixes
- **OpenAI chat completions now obey one terminal contract across every audited stream and non-stream path.** Successful passthrough streams that contain useful output but end at `[DONE]` or a clean EOF without `finish_reason` now receive exactly one synthetic terminal (`tool_calls` when tools were emitted, otherwise `stop`) before exactly one sentinel; empty, in-band-error, socket-failed and stalled streams are never disguised as success, and same-format Responses streams retain their structured `response.failed` lifecycle. Provider reasons are normalized to OpenAI's closed enum across Gemini/Antigravity, Claude, CommandCode, Ollama, native-compatible JSON and forced SSE-to-JSON bridges, so values such as `max_tokens`, `other`, `unknown`, `load` and `unload` cannot leak to strict clients. CommandCode also suppresses repeated finish/error terminals.
- **Blocked and non-streaming Antigravity responses now match the streaming contract.** Candidate-less prompt blocks, including `MODEL_ARMOR` and `IMAGE_SAFETY`, emit one `content_filter` terminal instead of closing with only `[DONE]`; non-stream `MAX_TOKENS` returns `length`, sanitized function names are restored exactly (for example `a_b` back to `a/b`), and Gemini thought/cache accounting now reports thoughts as completion reasoning tokens and cached input in `prompt_tokens_details`.

# v1.0.263 (2026-08-21)

## Fixes
- **Antigravity streams now always finish with a valid OpenAI terminal chunk.** Antigravity can end with a candidate-less `usageMetadata` wrapper or a clean EOF instead of putting `finishReason` on the last candidate. The translator discarded that usage-only wrapper and returned nothing during flush, while the shared finalizer still emitted `[DONE]`; strict clients then failed with `OpenAI completions stream closed before a finish_reason was received`. Candidate-less usage is now retained, a missing clean terminal emits exactly one `stop` (or `tool_calls`) before `[DONE]`, and explicit Gemini reasons map to OpenAI's enum (`MAX_TOKENS` → `length`, safety reasons → `content_filter`) instead of leaking invalid values such as `max_tokens`.
- **Antigravity streamed tool calls now preserve the exact client-declared name.** Tool names were sanitized to Gemini's identifier grammar without a reverse map or collision handling, so `a/b` and `a b` both became `a_b`; the model could call a different/ambiguous name that the client never declared. Antigravity request translation now allocates deterministic unique names across both Gemini- and Claude-backed models, rewrites call/response history consistently, leaves valid names untouched, and restores each original name on the streamed response.

# v1.0.262 (2026-08-19)

## Fixes
- **Closing the Codex or xAI login modal never shut down its callback proxy.** `OAuthModal` fires `POST /api/oauth/{codex,xai}/stop-proxy` with no body when the modal closes, but the route parsed the JSON body before it looked at the action, so an empty body answered `400` and the `stop-proxy` branch was never reached. `stopCodexProxy()` therefore never ran: the loopback listener on `127.0.0.1:1455` stayed up until its 5-minute timeout, and reopening the modal inside that window hit `EADDRINUSE` and silently fell back to the legacy redirect path. A bodyless POST is now valid for `stop-proxy` only; every other action still requires a body, and `stop-proxy` still rejects providers that have no fixed-port proxy. Trae/Windsurf/Zed were unaffected because they call the same action over GET.
- **Buttons stopped looking clickable.** Tailwind v4 drops the default `cursor: pointer` on `<button>`, and the rule that used to restore it was lost, so controls rendered with the plain arrow. Measured on a live dashboard: 13 of 26 enabled buttons on the providers page had `cursor: default`, including the theme toggle, the language switcher and the collapsed nav section headers — icon-only controls that have no label or button chrome to signal affordance on their own. Restored globally for `button`, `[role="button"]`, `label[for]`, `summary` and `select`, plus `cursor: not-allowed` for disabled controls, which was also missing. Verified across three pages: 141 enabled buttons now report `pointer` and 10 disabled ones report `not-allowed`.

# v1.0.261 (2026-08-19)

## Fixes
- **Two API routes were missing from the published package, so `/dashboard/pxpipe` answered 500.** `cli/scripts/build-cli.js` matched its exclude patterns against a bare entry name at every depth, and `"logs"` (meant for runtime log folders) also matched Next's compiled route directories: `api/pxpipe/logs` and `api/usage/logs` were dropped from the bundle. The build produced 216 route files and the package shipped 214, so v1.0.260 answered `500 MODULE_NOT_FOUND` on routes that had compiled correctly. On the PXPIPE page the damage went past the log panel: `PxpipeClient` fetches status, stats and logs in one `Promise.all`, so the failed `.json()` threw and the `/api/pxpipe/health` call after it never ran, leaving status and health blank too. `/api/usage/logs` fails the same way for the media-provider combo page. Exclusion is now scoped by location — junk files (`.env*`, `*.log`, `.DS_Store`) still match at any depth, dependency directories (`sharp`, `@img`, `detect-libc`) only inside `node_modules`, and runtime state (`logs`, `tmp`) only at the bundle root or inside `node_modules` — so a route segment that happens to share one of those names survives. The predicate moved to `cli/scripts/bundleExcludes.js` and is covered by tests.
- **The dashboard never told the browser which language it was showing.** `src/app/layout.js` renders a fixed `lang="en"` shell and translation happens client-side by rewriting text nodes, but nothing ever updated `<html>`: picking Arabic, Hebrew or Urdu produced translated text inside a left-to-right layout, with `lang` still reporting English to screen readers (WCAG 3.1.1). `initRuntimeI18n` and `reloadTranslations` now publish the active locale on `<html>` and set `dir` from the new `getLocaleDirection` helper, so RTL locales lay out right-to-left — verified in a browser, where the sidebar moves from 0–288px to 1152–1440px at a 1440px viewport.

# v1.0.260 (2026-08-19)

## Features
- **`/v1/models` now advertises capabilities and token limits.** The response was just `{id, object, owned_by}`, so any client that auto-discovers models had nothing to work with — OMP's `openai-models-list` discovery fell back to conservative defaults (small context window, tools assumed off) and then aborted large tool-using requests against models that handle them fine: `kr/claude-opus-5` chats normally when called directly, but OMP dropped the connection at 96 ms with `IN 0 / OUT 0`. Each LLM entry now carries pattern-matched `capabilities` plus `context_length` and `max_completion_tokens` under the snake_case names the OpenAI convention uses (a client matching `context_length` previously found nothing, guessed from the model name, guessed high, and so never compacted before hard-failing upstream). Verified live: 164/228 models expose metadata, up from 0; the rest are non-LLM kinds with no context window.

# v1.0.259 (2026-08-19)

## Features
- **Kiro's static model catalogue now matches what Kiro actually offers.** The `kr` list held 12 entries against 9router's 44, so API-key accounts — which are served the static list because their live catalogue is unreadable without a CodeWhisperer profile — were missing most of the lineup. Added, tracking kiro.dev/changelog/models: Opus `claude-opus-5`, `4.8`, `4.7` and `4.5` (the last was commented out) each with thinking/agentic/thinking-agentic variants; `claude-sonnet-5` plus variants; and `gpt-5.6-sol` / `terra` / `luna` (272k context) plus variants. Verified on a live build: `kr/*` went from 12 to 44 models — 14 base and 30 variants.

# v1.0.258 (2026-08-19)

## Fixes
- **Kiro API-key accounts no longer vanish from `/v1/models`.** Kiro headless-auth (API key) accounts chat fine but have no CodeWhisperer profile: `ListAvailableProfiles` answers `200 {"profiles":[]}`, so `profileArn` can never be resolved and `ListAvailableModels` then rejects every catalog request with `400 "Invalid profileArn"`. Because Kiro's live per-account union was the only source, the entire `kr/*` namespace disappeared and the provider looked gone — even though `kr/claude-sonnet-4.5` answers a real chat request. `resolveKiroModelIds` now counts connections whose catalog is *structurally* unreadable (no `profileArn`) and falls back to the static catalog for that case only; accounts that do have a `profileArn` keep the verified-only union, so a transient failure still never invents models. Verified live: `kr/*` went from 0 to 12 models. This also settles a contradiction in the codebase — `kiroModels.js` documented that callers "can fall back to the static catalog" while `route.js` asserted they "must never".

# v1.0.257 (2026-08-19)

## Fixes
- **The Kiro connection Test button always failed with "Provider test not supported".** Kiro headless-auth keys are stored with `authType: "apikey"`, so a test lands in `testApiKeyConnection`'s switch — but Kiro's format is `kiro`, not `openai`, so it fell through to the default branch and no key could ever pass. That made a dead token indistinguishable from an unsupported provider. Added a `kiro` case using the existing `KiroService.validateApiKey` (a key is valid when its CodeWhisperer profile resolves — the same rule `/api/providers/validate` got in v1.0.255). Honours the connection's region, defaults to `us-east-1`, and surfaces the upstream message instead of a generic failure.

# v1.0.256 (2026-08-19)

## Fixes
- **The Kiro API-key form is inline in the auth modal again.** v1.0.255 collapsed the provider page to a single *Add Connection* button (correct) but also rerouted the modal's "Kiro API Key" card to the shared `AddApiKeyModal` (not asked for, and not how 9router behaves — it keeps an inline key + region form inside its own auth modal, which this modal already had). `KiroAuthModal` and `KiroOAuthWrapper` are restored to their pre-v1.0.255 shape: choosing the card shows the key/region form inside "Connect Kiro" and posts to `/api/oauth/kiro/api-key`. The single-button change and the `validate` route fix from v1.0.255 are kept.

# v1.0.255 (2026-08-19)

## Fixes
- **The Kiro API-key Check button could never succeed.** `/api/providers/validate` dispatches per provider, and its `default` branch only probes providers with `format: "openai"`. Kiro's format is `kiro`, so every request fell through and answered `400 "Provider validation not supported"` — the modal's Check button was impossible to satisfy even with a valid key. Added a `kiro` case that uses the existing `KiroService.validateApiKey` (a key is valid exactly when its CodeWhisperer profile resolves).

## Improvements
- **One "Add Connection" button on the Kiro provider page instead of three.** The page exposed *Open Automation*, *OAuth* and *API Key* side by side, where the auth modal already lists every method — including a "Kiro API Key" card. That card POSTed straight to `/api/oauth/kiro/api-key`, so choosing it silently gave up custom name, priority, proxy pool and Bulk Add. The card now defers to the shared `AddApiKeyModal`, and the standalone button is gone: one entry point, no lost capability. Collapsing is gated on `authModalCoversApiKey` (Kiro only) — the other dual-auth providers (`codebuddy`, `codebuddy-cn`, `qoder`, `trae`, `windsurf`) open the generic `OAuthModal`, which has no API-key path, so they keep both buttons. *Open Automation* is untouched.

# v1.0.254 (2026-08-19)

## Performance
- **Kiro requests can finally hit the upstream prompt cache.** Kiro/CodeWhisperer caches on an exact prefix match, and two things guaranteed a miss on every turn: `conversationId` was a fresh `uuidv4()` per request (so each turn looked like a brand-new conversation), and the volatile `[Context: Current time is …]` marker was prepended to the *first* user message (so msg0 changed every second and the prefix never matched again even within one conversation). Added `kiroSessionReplay` (ported from 9router, adapted): the first user message is frozen per session and replayed byte-identically on later turns, while the timestamp is applied to the live turn only. Since no conversation id arrives from the client, one is derived deterministically from connection + opening message. The frozen msg0 also carries the tool specs, so the cache key includes a tool fingerprint — a changed toolset invalidates the entry instead of shipping stale specs. Verified across three turns: `conversationId` stable, msg0 byte-identical, timestamp present only on the current message.

# v1.0.253 (2026-08-19)

## Features
- **Translators can now register a direct `source:target` route, skipping the OpenAI pivot.** Native-to-native pairs (e.g. `claude:kiro`) previously round-tripped through OpenAI, which has to flatten provider-native blocks into OpenAI messages and rebuild them — the lossy step behind several recent tool-call fidelity bugs. When a translator is registered for the exact pair it is used in a single hop, on both the request and response side. Guarded to pairs where neither side is OpenAI, since a single registry hop already *is* the direct route and routing it here would skip the OpenAI-intermediate request log.

## Fixes
- **Claude extended-thinking requests no longer vanish in translation.** `claudeToOpenAIRequest` built its result from scratch and never copied `body.thinking`, so any provider reached through the `claude→openai→X` pivot lost the request entirely: Kiro never saw its `<thinking_mode>` trigger even when the client explicitly asked for thinking, and OpenAI-shaped providers got no `reasoning_effort`. Enabled thinking now maps to `reasoning_effort`, scaled by `budget_tokens` using the same thresholds `antigravity-to-openai.js` already applies. An explicit `type: "disabled"`, or no `thinking` field, still yields nothing.
- **Kiro tool calls no longer break on MCP tool names.** Kiro/CodeWhisperer only accepts names matching `[a-zA-Z0-9_-]{1,64}`, which MCP tools routinely violate (`tool with spaces`, `mcp__srv__do.thing`, names over 64 chars) — an invalid spec makes Kiro reject the entire request. Names are now sanitized (invalid characters collapsed to `_`, trimmed to 64, de-duplicated) across both the tool specs and the `toolUses` in history, so a history entry always refers to a tool Kiro was actually given. The reverse map travels as a non-enumerable `_toolNameMap` and `kiro-to-openai` restores the client's original name, so a renamed call still matches the client's declaration instead of being silently dropped.

# v1.0.252 (2026-08-19)

## Fixes
- **WebSearch no longer comes back empty on non-Anthropic providers.** Anthropic's built-in *server* tools (`web_search_20250305`, `code_execution`, …) carry a `type` and no `input_schema` because Anthropic executes them itself. `claudeToOpenAIRequest` converted every tool to an ordinary `type: "function"`, so on a `claude→antigravity` request `web_search` was forwarded to Google as a client function with a placeholder `{reason}` parameter. The model duly "called" it, nothing executed a search, and the tool returned nothing — surfacing as a WebSearch that runs but yields no results. Built-in server tools are now dropped when translating to a provider that cannot run them (matching the filter already applied on the target-Claude path in `prepareClaudeRequest`), while client tools translate as before and built-ins still pass through when the target is Anthropic itself.

# v1.0.251 (2026-08-19)

## Fixes
- **Antigravity anti-ban cloaking no longer corrupts tool names, enums, or tool-call data — the cause of degraded Claude Code output.** v1.0.250 stopped the obfuscator from mangling schema *property keys*, but it still walked every other string, so a zero-width joiner (U+200D) landed in three places that must stay byte-identical. (1) **Declared tool names**: `cursor_move` → `c<ZWJ>ursor_move`, and since nothing de-obfuscates the response, the model echoed the cloaked name back in `functionCall.name`, matched no client-side tool, and the call was dropped. (2) **`enum` / `const` values**, which the model must reproduce exactly. (3) **Tool-call payloads** — `functionCall.args`, `functionResponse.response`, Claude `tool_use.input` / `tool_result.content` — meaning file paths (`/Users/x/c<ZWJ>ursor/app.js`), shell commands (`cd /opt/c<ZWJ>ursor`), and file *contents* were altered, so the model read garbled source and wrote the ZWJ back to disk. Identifiers and tool-call data now pass through verbatim; prompts, message text, and tool descriptions are still cloaked, so anti-ban behavior is unchanged.

# v1.0.250 (2026-08-18)

## Fixes
- **Antigravity anti-ban obfuscation no longer corrupts tool schema property keys (the real cause of the `tools.N.custom.input_schema.properties` 400).** The obfuscator inserts a zero-width joiner (U+200D) into sensitive words (`cursor`, `windsurf`, `claude code`, …) so Google can't flag the account. It ran last in `AntigravityExecutor.execute` by round-tripping the *entire* body through a string replace — so a tool whose property was literally named `cursor` (e.g. OMP's `hub` tool) became `c<ZWJ>ursor`, which violates `^[a-zA-Z0-9_.-]{1,64}$` and 400'd on Anthropic-on-Vertex. This was independent of, and downstream from, the v1.0.249 property-key sanitizer, so that fix never caught it. `obfuscateRequestBody` now walks the structure and obfuscates only string *values*, preserving object keys and schema property-name references (`properties`, `patternProperties`, `$defs`, `required`, `propertyNames`, …). Free-text content (prompts, descriptions, string enums) is still obfuscated, so anti-ban behavior is unchanged.

# v1.0.249 (2026-08-18)

## Fixes
- **Claude tool calls no longer 400 on non-standard property keys when the client sends Claude-native format.** When a Claude-native client (e.g. Claude Code via `/v1/messages`) targets a Claude model, `sourceFormat === targetFormat`, so `translateRequest` skips the whole source→openai→claude translation — and with it the `openai-to-claude` property-key sanitizer. Tools with `input_schema` property keys violating `^[a-zA-Z0-9_.-]{1,64}$` (spaces, slashes, unicode, >64 chars — common in MCP tools) reached Anthropic-on-Vertex verbatim and 400'd with `tools.N.custom.input_schema.properties: Property keys should match pattern`. The same request from an OpenAI-format client was fine because it went through the sanitized path. `prepareClaudeRequest` now sanitizes `input_schema` property keys for every `target=claude` request regardless of source format.

# v1.0.248 (2026-08-18)

## Fixes
- **Antigravity streaming token usage no longer gets dropped.** Providers using `stream_options.include_usage` send token counts in a final separate chunk (`choices: []`) *after* the `finish_reason` chunk. `openaiToAntigravityResponse` stashed that usage into `state._usage` and returned `null` — but nothing downstream emitted after finish, so the Antigravity client received empty/zero `usageMetadata`. The trailing usage-only chunk now emits a proper `usageMetadata` response (empty candidates), and usage building is shared via a `buildUsageMetadata` helper.

## Chores
- **Removed dead Antigravity tool-cloaking code.** The `AntigravityExecutor.cloakTools` path was disabled in `translator/index.js` but its method, the `AG_DECOY_TOOLS` array, and the now-unused `AG_DEFAULT_TOOLS`/`AG_TOOL_SUFFIX` constants remained. Deleted them (Claude cloaking via `CLAUDE_TOOL_SUFFIX`/`CC_DEFAULT_TOOLS` is unaffected).

# v1.0.247 (2026-08-18)

## Fixes
- **The Antigravity tool property-key fix now actually ships.** v1.0.245/246 had the correct source fix, but Next's persistent webpack cache (`.next-cli-build/cache`) emitted a stale compiled chunk, so the published bundle kept the pre-fix code and OMP still hit `400 tools.N.custom.input_schema.properties: Property keys should match pattern`. `build-cli` now wipes the entire Next dist dir before building, forcing a clean recompile every release. Verified the shipped chunk contains the `^[a-zA-Z0-9_.-]{1,64}$` sanitizer.

# v1.0.246 (2026-08-18)

## Fixes
- **Antigravity Claude tool calls now sanitize property keys on the path OMP actually uses.** v1.0.245 added key sanitization to `cleanJSONSchemaForAntigravity`, but Claude models on Antigravity (opus/sonnet, served via `/v1/messages`) build tools in Anthropic-native form through `openaiToClaudeRequestForAntigravity` → `openaiToClaudeRequest`, which never called that cleaner — so `tools.N.custom.input_schema.properties: Property keys should match pattern '^[a-zA-Z0-9_.-]{1,64}$'` still 400'd. Property-key sanitization is now a shared helper (`translator/helpers/schemaPropertyKeys.js`) applied on both the Gemini `functionDeclarations` path and the Claude `input_schema` path. Verified live end-to-end through the VPS router: a tool with a `"bad key"` property returns 200 with a successful tool call.

# v1.0.245 (2026-08-18)

## Fixes
- **Antigravity Claude models no longer 400 on tool calls with non-standard property keys.** Claude served through Antigravity (`req_vrtx_*`) rejects tool `input_schema` property keys that violate `^[a-zA-Z0-9_.-]{1,64}$` with `400 tools.N.custom.input_schema.properties: Property keys should match pattern`. Clients such as MCP tools surfaced through OMP emit keys with spaces, slashes, colons, brackets, unicode, or more than 64 characters, which the router forwarded verbatim. `cleanJSONSchemaForAntigravity` now renames offending property keys (illegal chars → `_`, truncate to 64, collision suffix) and rewrites the sibling `required` list. Verified live against upstream: a bad key 400s, the sanitized key returns 200.

# v1.0.244 (2026-08-18)

## Fixes
- **Antigravity accounts no longer lock for ~13 minutes on a sub-second rate limit.** Google's RPM-burst 429s report a reset delay in milliseconds (`quotaResetDelay: "778.524243ms"`), but the parser's naive `.endsWith("s")` check treated the `ms` suffix as seconds and stripped only the trailing `s`, reading `778.524243` **seconds** — locking the account ~13 min for a limit that clears in under 1 s. With a pool sharing one licensed GSuite project (each account tolerating only ~2–3 concurrent requests), a brief burst locked every account in turn, surfacing as persistent `429 rate_limit_exceeded` while daily quota still showed 70–85% remaining. Durations are now parsed by unit (`ms` before `s`, plus compound `158h12m52.12s`), so the account is locked for the real reset (~0.8 s) and recovers immediately.

# v1.0.242 (2026-08-16)

## Fixes
- **Antigravity image generation now returns images.** The `ag/` image models were listed under Google-unknown names (`gemini-3.1-flash-image-preview`, `gemini-2.5-flash-image`) which 404'd. Renamed to `gemini-3.1-flash-image`, the id Antigravity's backend actually serves — text-to-image now works (transient upstream 500s are retried by the fallback loop).

# v1.0.241 (2026-08-16)

## Fixes
- **NVIDIA embeddings now work.** Two stacked bugs: the router stripped the `nvidia/` namespace from the model name (NVIDIA NIM needs the full `nvidia/nv-embedqa-e5-v5`, so the bare name 404'd), and it omitted the `input_type` parameter that NVIDIA's asymmetric embedding models require. The adapter now restores the namespace and defaults `input_type` to `query` (caller value respected when supplied).
- **Antigravity image generation is no longer rejected outright.** The router had no image path for Antigravity, so requests were refused with `400 "Provider 'antigravity' does not support image generation"`. Added the image adapter (delegating to the executor) plus the `image_gen` request envelope, so Antigravity image models (Nano Banana) now reach Google like the chat path does.

# v1.0.240 (2026-08-16)

## Fixes
- **Antigravity `ag/*` models no longer 400 on assistant prefill.** Gemini/Antigravity rejects a request whose contents end on a `model` turn with `Requests ending with a model turn are not supported` — which broke Claude Code's assistant-prefill (e.g. seeding `{`). The translator now merges consecutive same-role turns and appends a neutral user turn when contents would otherwise end on a model turn.

# v1.0.239 (2026-08-16)

## Improved
- **Routing-map active beam now matches upstream's palette** — cyan halo + bright-green plasma + soft-white core (the "electric" look), replacing the plain green edge.

# v1.0.238 (2026-08-16)

## Added
- **Antigravity and Vercel AI Gateway now appear on the Text-to-Image page.** Antigravity exposes its Nano Banana image models (`gemini-3.1-flash-image-preview`, `gemini-2.5-flash-image`) and Vercel AI Gateway gains image support — matching upstream's media-provider list.

# v1.0.237 (2026-08-16)

## Fixes
- **Trae and Windsurf are also hidden from the Providers page**, matching upstream (none of Trae/Windsurf/Zed/Devin are listed as chat providers there). Their executors stay wired so `trae/…` and `windsurf/…` still resolve if invoked directly.

# v1.0.236 (2026-08-16)

## Improved
- **Quota tracker cards now show auth-type / region / status badges** (e.g. `API Key · us-east-1 · active` for Kiro), matching the fuller layout of upstream.

## Fixes
- **Devin CLI and Zed are no longer listed on the Providers page.** Devin is a CLI tool that connects to the router (see CLI Tools), not a chat provider, and Zed is hidden upstream — both were mistakenly shown as providers.

# v1.0.235 (2026-08-16)

## Fixes
- **Kiro quota labels are now readable** ("Agentic Request", "Agentic Request (Free Trial)") instead of the raw `agentic_request` resource keys, tidying up the quota tracker.

# v1.0.234 (2026-08-16)

## Fixes
- **Antigravity `gemini-3.7-flash-*` no longer returns 404.** The previous release routed all Antigravity chat through the IDE fingerprint, but `gemini-3.7` is only served to the AGY CLI client and needs the CLI fingerprint — it now uses the CLI User-Agent again while every other model keeps the IDE fingerprint.

# v1.0.233 (2026-08-16)

## Fixes
- **Kiro quota now shows for API-key connections.** Kiro was recognized for usage tracking but missing from the API-key usage list, so a Kiro connection authenticated with an API key never rendered a quota card (only OAuth Kiro would). 
- **Provider logos on the routing map no longer go blank for newer providers** (Devin CLI, Trae, Windsurf, Zed, TokenRouter, Fish Audio): the routing-map node now uses the same icon fallback as the rest of the app (PNG → dynamic branded SVG → text initials).

# v1.0.232 (2026-08-16)

## Fixes
- **Antigravity chat now uses the IDE client fingerprint (`antigravity/ide/<version> <platform>/<arch>`) for every model.** Previously most models were sent an Electron/Chrome desktop User-Agent that Google flags as a third-party client, which broke Antigravity after updating. Quota requests keep their separate fingerprint.

# v1.0.231 (2026-08-16)

## Fixes
- **Provider logos no longer show a broken/garbled icon** when a `/providers/<id>.png` is missing — they now fall back to the dynamic icon route, which always renders a valid branded SVG.
- **The sidebar's Advanced section is expanded by default**, so its items are visible without a click.

## Improved
- **Animated routing map on the usage page**: active, last, and errored routes now render an animated "electric beam" edge (glowing halo → plasma → core, with particles traveling the path) instead of a flat dashed line.

# v1.0.230 (2026-08-16)

## Added
- **One-click browser sign-in for Trae, Windsurf, and Zed** alongside the existing paste-token connect. A local dynamic-port callback handles the login and exchanges the token server-side: Trae via its login-guidance flow, Windsurf via Firebase→Codeium key exchange, and Zed via a local RSA keypair that secures the callback. Zed is browser-only; Trae and Windsurf still accept a pasted token as a fallback.

# v1.0.229 (2026-08-16)

## Added
- **Windsurf, Zed, and Devin CLI providers** for chat. Windsurf (`ws`) runs over Codeium's gRPC-web backend — connect with a Codeium API key (`sk-ws-…`). Zed (`zd`) fronts Anthropic/OpenAI/Google/xAI via cloud.zed.dev — connect with `<user_id> <token>`. Devin CLI (`dv`) drives the locally installed `devin` binary over stdio and requires the Devin CLI logged in on the host. One-click browser login is not wired yet; connect via pasted token (or local binary for Devin).

# v1.0.228 (2026-08-16)

## Added
- **Trae (SOLO remote agent) provider** for chat, selectable as `trae/<model>` (alias `trae`/`marscode`). Connect by pasting your Trae Cloud-IDE-JWT token (grab it from the browser DevTools Authorization header on solo.trae.ai; the token lasts ~14 days). Browser one-click login is not wired yet — use the pasted token for now.

# v1.0.227 (2026-08-16)

## Added
- **Fish Audio and Xiaomi MiMo text-to-speech providers**. Fish Audio (`fish`) uses api.fish.audio with optional reference-voice cloning; Xiaomi MiMo TTS is exposed on the existing MiMo provider (`mimo`) with the `mimo-v2.5-tts` model. Both are configured with an API key on the media/TTS providers page.

# v1.0.226 (2026-08-16)

## Added
- **SAML SSO configuration UI** on the profile page: an SSO card with auth-mode selector, IdP entry-point/certificate/issuer/attribute fields, IdP metadata XML upload (auto-fills entry point + signing certificate), certificate file upload, and the ACS + SP-metadata URLs to register with the identity provider. A "Test configuration" action validates the settings without contacting the IdP.

# v1.0.225 (2026-08-16)

## Added
- **Native SAML 2.0 SSO** for dashboard login alongside existing OIDC/password: AuthnRequest initiation, an assertion-consumer (ACS) endpoint with signature verification and single-use replay protection, and an SP-metadata endpoint. Reuses the existing dashboard session; configured via settings (SSO type selector + `saml*` keys). Password login stays available as a recovery path.
- **TokenRouter and Alibaba Token Plan providers** (API-key, OpenAI-compatible), selectable as `tokenrouter/…` (alias `tr`) and `alitp-intl/…` (alias `alitp`).
- **Capacity Adapter for combo routing**: per-modality (vision/pdf/audio/video) global fallback pools that kick in when the target model can't handle a required input modality, with history trimmed to the fallback model's context window. Disabled by default (opt-in per modality; no traffic impact until configured).

# v1.0.224 (2026-08-16)

## Security
- **Web search now rejects client-supplied `baseUrl` overrides that point at non-public hosts** (loopback, RFC1918, link-local metadata `169.254.169.254`, IPv6 loopback, non-HTTP schemes), closing an SSRF path where a request body's `provider_options` could aim the server's own fetch at internal targets. Public overrides and defaults are unaffected.

# v1.0.223 (2026-08-16)

## Fixes
- **Kiro API key connections now show on the providers page** instead of a false "No connections" label: Kiro's provider definition now declares both `oauth` and `apikey` auth modes, so its API-key accounts are counted and the enable/disable toggle targets the right auth type.

# v1.0.222 (2026-08-16)

## Fixes
- **Anthropic-compatible providers now forward the client's `anthropic-beta` header**, so beta-gated request fields like Claude Code's `context_management` reach the upstream with their required flag instead of being rejected with `400 Extra inputs are not permitted`.

# v1.0.221 (2026-08-16)

## Fixes
- **Restored Claude and legacy Gemini access through Antigravity** by reserving the AGY CLI fingerprint for Gemini 3.7 chat while other chat models use the compatible desktop fingerprint; model-catalog and quota discovery remain on the CLI fingerprint.

# v1.0.220 (2026-08-15)

## Fixes
- **Fixed Antigravity Quota Tracker discovery for Gemini 3.7 Flash High, Medium, and Low** by using the official `agy 1.1.13` CLI fingerprint on model-catalog and quota requests.

# v1.0.219 (2026-08-15)

## Added
- **Antigravity now exposes Gemini 3.7 Flash High, Medium, and Low** across provider catalogs, MITM mappings, quota tracking, and the bundled CLI.

## Fixes
- **Antigravity chat requests now use the official `agy 1.1.13` CLI fingerprint** required for Gemini 3.7 access, while quota, OAuth, and project discovery retain their existing client identities.

# v1.0.218 (2026-08-14)

## Added
- **Qoder Personal Access Tokens now work across validation, model discovery, chat, connection tests, and quota tracking**: stored `pt-` credentials are exchanged for short-lived `jt-` runtime tokens, concurrent exchanges are coalesced, and job-token traffic uses Qoder's compatible API host without replacing the saved PAT.
- **Ollama Cloud, Kimi, and DeepSeek now expose authoritative live usage**: API-key connections participate in quota refresh, authentication and exhaustion remain distinct, and transient failures preserve the last successful snapshot as stale instead of fabricating zero balance.
- **Codex exposes model-aware `max` and `ultra` reasoning**: Sol and Terra accept `ultra`, Luna clamps it to `max`, unknown models clamp conservatively to `xhigh`, and explicit request effort keeps precedence over model suffixes and defaults.

## Fixes
- **Claude identity headers are isolated per request**: concurrent clients can no longer inherit another request's Claude Code session metadata, while authorization, cookies, and forwarding headers remain excluded.
- **OAuth credentials refresh independently of quota and chat traffic**: active expiring connections refresh with bounded concurrency, stale writes are discarded, permanent refresh rejection stops polling, and shutdown waits for an active sweep before database buffers close.

# v1.0.217 (2026-08-14)

## Fixes
- **CodeBuddy Global and CN quota balances now reconcile current-cycle and lifetime package data without inventing credits**: precise billing fields take precedence over rounded fallbacks, partially exhausted package sets stay usable without counting stale trial balances, and runtime `14018`/`11140` state remains authoritative.

# v1.0.216 (2026-08-13)

## Fixes
- **CodeBuddy CN routing now trusts confirmed chat failures over nominal billing balances**: the first upstream `11140` quarantines a rejected credential, `14018` marks the whole account exhausted for both editions, and stale successes or billing refreshes cannot silently reactivate it.
- **Quota Tracker now preserves the last successful snapshot without faking freshness**: failed refreshes keep usable quota rows with an explicit warning, while single-account and aggregate timestamps advance only when the server persisted newer data.
- **Manual CodeBuddy recovery is atomic**: explicitly re-enabling an account clears only runtime health markers and its stale quota snapshot while preserving credentials and provider metadata.

# v1.0.215 (2026-08-13)

## Fixes
- **CodeBuddy CN recurring quota usage is now internally consistent**: monthly credits derive usage from the provider's cycle total and remaining fields, so exhausted accounts report `500/500` used instead of mixing in unrelated lifetime package counters.

# v1.0.214 (2026-08-13)

## Fixes
- **CodeBuddy Global quota tracking now follows authoritative chat exhaustion**: upstream error `14018` creates a recoverable account-wide runtime marker, immediately reports effective zero credits despite a nominal billing balance, and clears only after a successful bounded chat probe. CodeBuddy CN behavior is unchanged.
- **Codex connections no longer retry rejected refresh tokens inside the expiry lead window**: a durable refresh-block marker now takes precedence over proactive refresh timing, preserving still-valid access tokens until the account genuinely needs reconnection.

# v1.0.213 (2026-08-13)

## Fixes
- **CodeBuddy Global quota tracking now discovers newly introduced billing packages from API keys**: runtime and quota-cookie requests no longer filter upstream results through a stale hard-coded package-code list, so current plans such as Pro Plan Trial Subscription remain visible in Quota Tracker.

# v1.0.212 (2026-08-13)

## Features
- **CodeBuddy Global can now discover live and hidden model routes from the provider page**: a new control beside `Add Model` refreshes Tencent's latest official manifests for free, then optionally scans selected hidden candidates against one explicitly chosen Global connection with bounded concurrency, credit-use confirmation, result classification, and one-click persistence. CodeBuddy CN remains unchanged.

# v1.0.211 (2026-08-13)

## Fixes
- **CodeBuddy catalog synchronization no longer hides verified live routes**: the current CLI manifest remains the baseline while Claude Opus/Sonnet/Haiku and other smoke-verified global routes stay available, and CodeBuddy CN exposes both `kimi-k3` and `kimi-k3-1`.

# v1.0.210 (2026-08-13)

## Fixes
- **Provider model tests now stay on the selected connection**: probes use authenticated internal account pinning, run with bounded concurrency, and stop scheduling CodeBuddy models when the selected credential is rejected with upstream error `11140`.
- **CodeBuddy model catalogs now match the official CLI `2.135.0` manifests**: global and CN chat routes expose the current model IDs and token limits without mixing in image or retired models.

# v1.0.209 (2026-08-13)

## Fixes
- **CodeBuddy global and CN requests now use the latest verified CLI fingerprint**: chat and OAuth refresh paths consistently identify as CodeBuddy CLI `2.135.0`.

# v1.0.208 (2026-08-09)

## Fixes
- **Remote Codex OAuth now uses device authorization**: dashboards opened through a VPS IP or domain no longer require copying a localhost callback URL; complete the one-time device code flow in the browser instead.

# v1.0.207 (2026-08-08)

## Fixes
- **Notion AI now repairs its native Chrome-TLS runtime on bounded installs and router startup**: minimal/VPS deployments no longer silently fall back to a transport that Notion rejects with an HTTP-200 in-band error.

# v1.0.206 (2026-08-08)

## Fixes
- **Notion AI streaming failures are no longer reported as successful assistant text**: HTTP-200 error frames now become structured stream errors, update observability and account lifecycle state correctly, and skip false usage records.

# v1.0.205 (2026-08-08)

## Fixes
- **Notion AI is now usable from the provider dashboard**: the experimental cookie provider is included in the published UI build, resolves workspace and model metadata from the authenticated session, uses a resilient TLS fallback, and keeps multi-turn threads stable.

# v1.0.204 (2026-08-08)

## Fixes
- **Codex refresh failures are now durable and actionable**: `invalid_refresh_token` is treated as a permanent rejection, retry loops stop, live quota can continue using a still-valid access token, and the rejected credential is blocked until the account is reconnected.

# v1.0.203 (2026-08-08)

## Fixes
- **Codex quota windows now use the upstream schedule**: a primary window with a seven-day duration is shown as weekly instead of being mislabeled as a session window, so quota reset dates and optional auto-ping behavior match the account's real limit.

# v1.0.202 (2026-08-08)

## Fixes
- **Codex OAuth refresh metadata now survives connection creation**: new OAuth connections retain the rotating refresh token's ID token and refresh timestamp, preventing the router from treating them as stale and forcing unnecessary reauthentication.

# v1.0.201 (2026-08-07)

## Fixes
- **Blackbox routing now follows the provider's current catalog and validates keys reliably**: stale model IDs are replaced with the live provider-qualified catalog, while credential checks use Blackbox's LiteLLM key-info endpoint instead of spending tokens on a chat probe.
- **Blackbox spend is visible in Quota Tracker and provider tests no longer block each other**: capped and uncapped key usage is normalized from upstream budget data, independent provider/model test buttons can run concurrently, and batch connection checks use bounded parallelism while preserving result order.

# v1.0.200 (2026-08-07)

## Fixes
- **Codex subagents now start without a rejected first attempt**: Quick Setup and its manual snippet keep the main model explicitly namespaced as `cx/...`, but write `agents.default_subagent_model` as the canonical bare picker ID required by Codex's local `spawn_agent` validator. Child requests are still safely namespaced to `cx/` by the Codex-client routing guard added in v1.0.199.

# v1.0.199 (2026-08-07)

## Fixes
- **Codex CLI `/model` selections can no longer be hijacked by stale aliases**: bare models sent by Codex are namespaced to the `cx/` OAuth provider before alias resolution, while other clients keep their existing routing. Startup maintenance removes aliases that reference deleted compatible providers, and deleting a provider now atomically removes its aliases and custom models so the failure cannot recur.

# v1.0.198 (2026-08-07)

## Fixes
- **Codex CLI setup now writes configuration that current Codex releases accept and can route through ZevaiRouter**: dashboard and terminal setup default to `cx/gpt-5.6-sol`, normalize the Responses endpoint without duplicate `/v1` suffixes, migrate the malformed legacy `[agents.subagent]` model field to `agents.default_subagent_model`, remove stale provider sections, and protect file-based API credentials with owner-only permissions.
- **Audited build dependencies no longer carry known vulnerabilities**: the lockfile now resolves patched JS-YAML, PostCSS, Nano ID, and Tailwind build packages without forcing a framework upgrade.

# v1.0.197 (2026-08-06)

## Fixes
- **Headroom's dashboard status now distinguishes active compression from a standby proxy**: disabling the integration shows `Disabled · Proxy ready` while the local process remains available, and only reports `Enabled` when routing compression is actually active and healthy.

# v1.0.196 (2026-08-06)

## Added
- **Headroom is now a one-toggle setup**: enabling context compression installs `headroom-ai` into a ZevaiRouter-managed Python directory, starts the local `/v1/compress` proxy, verifies its health before saving the setting, and automatically resumes it after router or system restarts without modifying global Python packages.

# v1.0.195 (2026-08-06)

## Fixes
- **Request logs now use one concise correlated lifecycle**: each request emits a stable session tag, request summary, active transforms, and final latency/token result without duplicate routing, pending, usage, or stream-complete noise; errors remain visible.
- **Headroom can be disabled reliably when its service is unavailable**: the dashboard toggle follows the persisted setting instead of proxy health, reports save failures, and no longer leaves a misleading enabled state after a failed update.

# v1.0.194 (2026-08-06)

## Fixes
- **Provider logos no longer render as broken images**: missing provider assets now fall back through a dynamic icon route with aliases or generated badges, while mislabeled Nebius and SiliconFlow images are shipped as valid PNG files.
- **CodeBuddy CN registration survives flaky proxy startup and reliably selects Hong Kong numbers**: proxy egress checks retry full warm-up rounds through one sticky agent with an additional IP fallback, common country-tagged usernames align the CloakBrowser profile without a second lookup, and the phone flow selects and verifies the exact `+852` row before restoring it if Keycloak resets the selector to `+86` after input. The job summary now reports actual successes instead of labeling every terminal account as completed.

# v1.0.193 (2026-08-05)

## Added
- **API-key connection assignments**: each client key can now be restricted to selected provider connection IDs from the Endpoint dashboard. Empty assignments remain unrestricted; non-empty assignments constrain chat, combos, fallback, search/fetch, embeddings, image, audio, and video routing to the selected subset while preserving budgets, RPM, and allowed-model policies.
- **Grok Build dashboard integration**: configure Grok Build's main model plus independent general-purpose, explore, and plan subagent models through ZevaiRouter. Existing TOML sections are preserved, owned slots are idempotent, and Reset restores the previous Grok Build mappings.
- **Agent Skills dashboard**: copy raw skill URLs directly from `/dashboard/skills`; bundled skills now cover chat, image, video, audio, embeddings, web search, and web fetch.
- **Unified provider and capability registry**: provider transport, model, media, and display metadata can now migrate incrementally into canonical registry modules without breaking existing consumers. The first canonical provider is xAI, including its LLM, image, search, OAuth/API-key, and video surfaces.
- **Seven canonical OpenAI-compatible providers**: Poolside, Kilo Gateway, LLM7, Bazaarlink, Morph, Tencent Hunyuan, and Baidu Qianfan now ship with shared transport, validation, dashboard, alias, model, and context metadata instead of disconnected or hidden config fragments.
- **Automatic model capability enrichment**: `/v1/models/info` resolves context/output limits, reasoning, tool calling, and input/output modalities from provider/live metadata first, then safely enriches missing values from the HTTPS-only `models.dev` catalog. Catalog downloads are bounded, cached for six hours, deduplicated, and never globally guess an ambiguous model ID.
- **xAI Grok Imagine video**: added async generation, edit, extension, and status endpoints under `/v1/videos`, plus `zevai xai video` for submit/poll/download. Billable creation requests are never replayed after ambiguous network/5xx failures; job polling stays pinned to the account returned by the create response. The media dashboard now polls, previews, and downloads completed videos automatically.
- **Translator golden gates**: request and streaming-response snapshots now cover OpenAI, Responses API, Claude, Gemini, Kiro, and Ollama translation shapes, including reasoning, usage, and tool calls.

## Fixes
- **Gemini CLI tool calling and reasoning settings are no longer dropped or malformed**: translated tool requests now set validated function calling, Gemini 3 uses `thinkingLevel` with camel-case `includeThoughts`, configured safety settings survive translation, and all supported OpenAI output-token fields feed the resolved model limit.
- **xAI video no longer disappears from the dashboard**: the old API-key-only xAI metadata duplicate could overwrite the canonical OAuth/API-key registry entry and remove its video service kind. xAI now has one metadata source.

# v1.0.192 (2026-08-03)

## Fixes
- **Tool calls are now visible in observability logs**: streaming and non-streaming responses preserve normalized function call IDs, names, and arguments across OpenAI, Claude, Gemini, Responses API, and translated provider formats. Console Log emits a safe `[TOOL]` summary without arguments, Request Details renders each call explicitly, and oversized records retain bounded tool metadata instead of dropping it with the response payload.

# v1.0.191 (2026-08-03)

## Fixes
- **CodeBuddy CN model limits now match the destination catalog**: Kimi K3 and GLM 5.2 advertise 1M-token input contexts, DeepSeek V3.2 uses 96k/32k input/output limits, and DeepSeek V4 Pro/Flash use 1M/50k instead of the stale 128k/8k values.
- **CodeBuddy token metadata updates automatically from the official CLI**: the router discovers installed `@tencent-ai/codebuddy-code` product manifests, overlays validated limits onto the smoke-tested model allowlist, exposes the detected metadata through model-list/info APIs, and falls back to safe static values without reintroducing internal or retired routes.

# v1.0.190 (2026-08-03)

## Fixes
- **A cancelled proxy tunnel no longer crashes the router**: the SOCKS-to-HTTP bridge holds a raw `CONNECT` socket while it dials the upstream, which can take up to 25s, and attached its socket handlers only *after* that dial succeeded. A raw socket has no default `error` handler, and Chromium aborts tunnels constantly during bulk import — a closed context, a cancelled navigation — so the resulting `ECONNRESET` arrived with nothing listening and surfaced as an `uncaughtException` that killed the whole process. Every proxied bulk import was one aborted request away from taking the router down.
- **A CodeBuddy account that already had a key no longer saves a keyless connection**: `finalizeCodeBuddySuccess()` skips key creation when `findExistingApiKey()` turns one up in the database, then dropped `generatedApiKey` from the payload because there was no creation to record — but nothing else carried the key, so `defaultSaveCodeBuddyConnection()` saw neither `generatedApiKey.key` nor `apiKey` and wrote a connection with no key at all. It was finalized as `success` with `testStatus: "active"` and could not serve a single request. The key found by the lookup now reaches the row.
- **A dead proxy on one account no longer takes the router process down**: the Qoder and Antigravity bulk importers created their token-capture promise (the device-token poll, the OAuth callback monitor) before the automation call that eventually reads it, and each rejects on its own 3-minute deadline. Anything that threw in between — a `page.goto` timing out after 60s, a dead proxy — reached the catch that finalizes the account and closes the context, leaving that promise pending with no reader; it then rejected unattended minutes later, and an unhandled rejection is fatal on current Node. The other three managers already parked theirs; these two now do the same.
- **A solved captcha now lets the CodeBuddy CN registration continue**: after the operator was handed the browser, `waitForSmsRequestSignal()` was resumed with `returnOnCaptcha: false` and required the captcha to be *gone* before it would accept the send-button's disabled/countdown state. `CAPTCHA_SELECTORS` include `[class*="captcha"]` and `[id*="captcha"]`, and Tencent's TCaptcha leaves its wrapper in the DOM after the slider is solved, so the check kept matching forever. The SMS had already been sent and the paid 5sim number was then cancelled as a failure. A genuine acknowledgement now wins on the resumed pass.
- **A manual-assist window is no longer closed out from under the operator**: `registerWithPhone()` mapped both post-handover outcomes — a form error raised after the browser was handed over, and the SMS-request captcha timeout — to `status: "failed"`. The manager's failure branch cancels the paid 5sim order, drops `account.manualSession` and closes the very context the operator is working in, so a login that was one click from finishing was destroyed mid-session. Both now return `needs_manual` and leave the session open; `waitForSmsRequestOutcome()` already flagged them with `needsManual`, which was being discarded.
- **Page chrome no longer counts as a Keycloak form error**: `checkIframeError()` substring-matched the bare words `Invalid` and `incorrect` against the whole document, and `getPhoneFrame()` falls back to `page.mainFrame()` when Keycloak renders top-level — so nav, footer and the 200-row country list were all in scope ("Invalid Bay Islands" is enough). A false hit there outranks a genuine SMS acknowledgement and fails the account. English markers must now appear as a real phrase about the code, the phone number or the credentials.
- **The right agreement checkbox is checked on CodeBuddy CN**: `ensureAgreementChecked()` took `page.locator(".t-checkbox").last()`, so a build rendering more than one checkbox (region opt-in, "remember me", marketing consent) had the wrong box toggled while the function still reported `ok`. The real agreement stayed unchecked, the OTP was never sent, and the paid number burned out as `sms_timeout`. The lookup now prefers the row carrying the agreement copy and falls back to the last *visible* checkbox rather than to whatever is last in the DOM.
- **A paid 5sim number is no longer left hanging by a manual follow-up**: the CodeBuddy CN `needs_manual` path handed the account to `runCnManualFollowup()` with the order still open — it can only finish the order when our own SMS poll already succeeded, and the captcha and `captcha_timeout` paths hand over before that. Nothing downstream settled it, so the number was abandoned until 5sim expired it: no refund on one that was never used, and no finish on one that was. The follow-up now owns the order when the caller could not settle it, finishing it when the operator completed the login and cancelling it (which is refundable) otherwise, and surfaces a `fivesim_settle_failed` step when 5sim will not confirm either call.
- **A full disk no longer kills a bulk-import run**: `persistJobSnapshot()` is invoked as `void this.persistJobSnapshot(...)` from ten callsites for the cheap non-preview writes, so the returned promise had no handler. The write underneath is a synchronous `fs.writeFileSync` with no guard of its own, and it throws on ENOSPC, EACCES and EROFS — a full disk therefore turned a snapshot write into an unhandled rejection, which is fatal on current Node. The whole run died mid-account and took the router process with it, over a write whose only job is to refresh the dashboard view. The failure is now recorded on the job instead.
- **Closing a context behind an authenticated SOCKS proxy no longer pins the worker forever**: the SOCKS-to-HTTP bridge's `close()` awaited `server.close()`, which stops accepting new connections and then waits for the open ones to end — but a CONNECT tunnel never ends on its own, and Chromium holds idle keep-alive tunnels open for the life of the context. That await therefore never returned, and it runs inside the patched `context.close()` *after* its bounded-close timeout, so the promise every caller awaits never settled: the worker was pinned for the life of the process rather than merely losing a proxy. Tunnel sockets are now tracked and destroyed before the close is awaited.
- **A Grok CLI manual assist no longer orphans a Chromium process**: `runManualFollowup()` closed the `context` it was called with and never touched `account.manualSession`. But `openManualSession()` relaunches a headless account headed, which replaces `manualSession.context`/`page` and parks the new browser on `manualSession.headedBrowser` — that browser belongs to the account, not to the shared one `runJob` cleans up. So the follow-up closed a context that had already been superseded and left a live Chromium holding its profile directory and memory until the router process itself exited. Every other manager already cleaned up through the `manualSession`-first shape; this one now does too.
- **A slow Cloudflare API no longer abandons live API tokens on the harvested account**: `createCloudflareHarvestPromise()` raced each attempt against a 12s budget, but `harvestCloudflareWorkersAiToken()`'s own request timeouts add up to 31s (8s accounts + 8s permission groups + 15s token POST). Through a residential proxy the budget routinely fired while that POST was in flight — and that POST creates a real API token. The token was created, discarded unrecorded, and the retry loop created another one, so a slow account accumulated live Workers AI tokens nobody could see or revoke. The per-attempt budget now clears the whole sequence (capped by the overall harvest deadline), and a creation that does land after the budget expired is revoked rather than left on the account.
- **A malformed 5sim purchase response no longer costs the balance**: `buyNumber()` threw on any payload missing `id` or `phone`, but a payload carrying an `id` with no usable `phone` means the purchase went through and the money is already spent. Callers only learn the order id from the return value, so throwing left nothing able to settle it and the number was abandoned until 5sim expired it, unrefunded. The order is now cancelled — which is refundable — before the bad shape is reported.
- **Antigravity manual assist no longer fails the moment the operator is handed the browser**: `openManualSession()` relaunches a headless account headed, replaces `manualSession.context`/`page`, then closes the old context — and calls `manualSession.rebind` so the OAuth callback monitor follows. The Antigravity manager never set that property, even though its monitor exposes `rebind` and the base manager already wires it for Kiro. The monitor therefore stayed bound to the context relaunch had just closed, so the operator's completed login was never observed and the account failed on the callback timeout, at the exact moment they were given a working window.
- **A Cloudflare manual assist can now actually finish**: `createCloudflareHarvestPromise()` polls a captured `page` rather than listening for events, so after `openManualSession()` relaunched the account headed and closed the original context, the harvest kept reading a dead target — `url()` returns empty and every `evaluate` throws — and could only ever end in its own timeout, right after the operator finished the login by hand. The harvest now exposes a `rebind` that the `needs_manual` branch hands to the manual session, the same way the event-driven monitors do.
- **A merely slow country dropdown no longer wastes a paid number**: `selectCountry()` raced its own work against `min(actionTimeout, 10_000)`, but the steps before the label search can spend 11s on their own (5s `waitFor` + 3s click + 3s options `waitFor`), so the race usually fired first and the caller treats a `false` as terminal — no retry, number cancelled. The budget now has a floor that fits the whole sequence, every step timeout is clamped to what is actually left, and the dropdown is reopened once when the option list was not ready on the first pass.

# v1.0.189 (2026-08-02)

## Fixes
- **A crashed server is now restarted on VPS and tray installs**: `startServer()` attached the child process's `error`/`close` listeners as its very last statement, but both tray mode and server-daemon mode (`ZEVAI_SERVER_MODE=1` or a non-TTY stdin — the systemd path every VPS install uses) `return` before reaching it. Those deployments therefore ran with no supervision at all: an OOM-killed or crashed server produced no restart and no crash log, and because the parent kept idling on its keep-alive interval, systemd's `Restart=always` saw a healthy main process and never intervened. The router simply stopped answering until someone noticed. Listeners are now attached immediately after the child is spawned, so every startup path is supervised.
- **`zevai update` no longer rolls back an install that actually succeeded**: every local health and version probe shelled out to `curl`, and a host without `curl` on `PATH` (minimal Debian/Alpine images, plain Windows) made all of them return `null`. That is indistinguishable from "the router is down", so a completed update was judged a failure and reverted. Probes now fall back to an out-of-process `node` HTTP request when `curl` is missing or unusable, while a `curl` that ran and genuinely failed to reach the endpoint is still treated as a failure.
- **Starting a second instance no longer kills the first one**: startup cleanup matched processes by looking for "zevai" in the command line, ignoring the port entirely, so `zevai --port 1998` terminated the instance already serving 1997. Cleanup now resolves ZevaiRouter CLI roots from the process table and keeps only those serving the target port, plus whatever actually holds the port (an orphaned child whose parent already died).
- **A failed update is no longer reported as success**: the in-menu update path exited with `r.status || 0`, and `spawnSync` reports `status: null` when the child is killed by a signal or fails to spawn. An updater OOM-killed mid-run therefore printed success and exited `0`. Signal deaths and spawn errors now exit non-zero with the cause.
- **`--port` and `--host` are validated instead of silently ignored**: `parseInt(args[i + 1], 10) || DEFAULT_PORT` fell back to 1997 for anything unparseable *and* still consumed the next argument, so `zevai --port --log` bound the default port and dropped `--log`; out-of-range values like `70000` were quietly replaced too. Both flags now go through the same normalizers the runtime-state parser uses, and a bad or missing value fails with a message rather than starting on a port you did not ask for.
- **The browser is no longer opened through a shell**: `openBrowser()` interpolated its URL into `open "…"` / `xdg-open "…"`, and that URL embeds `host`, which arrives from `--host`, `HOSTNAME`, or a systemd unit. A value containing a quote or `;` would have been executed as a shell command. The URL is now passed as an argv entry and must be `http`/`https` first.
- **Update checks no longer skip prerelease versions**: `compareVersions()` split on `.` and mapped to `Number`, so `1.0.188-rc.1` parsed as `[1, 0, NaN]`. Every comparison against `NaN` is false, making the version compare equal to whatever it was checked against, so the update was not offered. Versions are now parsed with the same leading-`v`/suffix-tolerant pattern the `/api/version` route uses, and an unparseable version compares equal rather than producing garbage.
- **A missing Xvfb during `zevai update` now says so**: the systemd unit rewrite looked up `Xvfb` and `xdpyinfo` on `PATH` and passed the result straight to the unit-escaping check, which reports `null` as "Xvfb path cannot be represented safely in the systemd unit: " — an escaping complaint for what is actually an uninstalled package, on a path that then rolls the whole update back. Both binaries are now checked for presence first, with the `apt-get` package that provides them.
- **Stopping the MITM proxy actually stops it**: `killProcess()` issued `kill -SIGTERM <pid>` through a shell, and the shell's `kill` builtin rejects the `SIG`-prefixed spelling — bash-as-`/bin/sh` answers "SIGTERM: invalid signal specification", dash answers "Illegal option -S". Only the `pkill -P` half (the server's children) ever landed, so the privileged proxy itself survived both the signal and `stopServer`'s escalation to force, which re-ran the same no-op. Signals now use the bare names both `kill` and `pkill` accept.
- **A wrong sudo password no longer disables MITM auto-restart for good**: the handler that detects a rejected password parked `mitmIsRestarting = true` to stop the retry loop, but that flag doubles as the "a restart is in flight" latch and nothing except an explicit stop ever cleared it. From then on every crash was ignored and the proxy stayed down until the router process was restarted. The block is now a separate flag that stops the retry loop and is lifted by the next successful start or stop. `scheduleMitmRestart()`'s four early returns and its throw path also each cleared the latch by hand; a single `finally` now covers them.
- **A MITM start that fails before spawning no longer retries forever**: the restart budget was forgiven based on `mitmLastStartTime`, which only moves when a child actually spawned. A start that threw earlier — port 443 held by another process, certificate trust failure — left it untouched, so the reset window always looked expired, the counter reset on every cycle and the five-attempt cap was never reached. Forgiveness now requires a spawn that happened since the last attempt *and* survived the reset window.
- **A failed privileged spawn no longer takes the whole router down with it**: the MITM child had no `error` listener, so an asynchronous spawn failure (`ENOENT` for a missing `sudo` or `node`, `EACCES`, `EAGAIN` under fork pressure) surfaced as an unhandled `error` event and killed the router process — not just the MITM start. The same path wrote the sudo password to the child's stdin with no guard, so a `sudo` that exited first raised an unhandled `EPIPE`. Both are handled now, and the spawn failure reason reaches the error the caller sees. The rejected-password branch also left `clearEncryptedPassword()` as a floating promise, whose rejection is fatal on current Node.
- **The MITM health check now honours its own timeout**: `pollMitmHealth()` had no wall-clock deadline and no per-request timeout, so a socket that connected and then went quiet left the caller awaiting a start that would never resolve or reject. It also treated the first `ok: false` body as terminal, failing starts that were merely still binding port 443. The poll now has a real deadline, per-request timeouts, and retries a transient not-ok body until that deadline.
- **Leftover-MITM cleanup matches paths with spaces again**: the `pkill -f` pattern was single-quote-escaped and then placed inside double quotes, so the escaping produced a string the shell re-split — an install path containing a space (`/Users/me/My Apps/…`) never matched, and the stale privileged process survived into the next start. The pattern is now passed as one properly quoted argument.
- **"Port 443 already in use by Google"**: `getProcessUsingPort443()` ran `lsof -i :443`, which lists outbound HTTPS connections as well as listeners, so the first row was usually whichever browser happened to be talking to the internet. The lookup is now scoped to listening sockets.
- **Manual assist no longer fails the Kiro login the moment it hands you the browser**: when a headless bulk-import account needs help, the flow relaunches headed, rebinds the OAuth callback monitor onto the new context, then closes the old one. The superseded context's `close` listener stayed armed and rejected the callback promise both readers were still awaiting, so the account was finalized as `failed_exchange` at the exact moment the operator was given a working window — collapsing the 15-minute manual window to zero and making manual assist impossible to complete. The monitor now tracks which context is current and ignores `close` events from contexts a rebind has replaced, while a close on the newest context still fails the wait as before.
- **Gemini function calling now works in the response direction**: incoming requests already translated Gemini `functionCall`/`functionResponse` parts into OpenAI `tool_calls`, but nothing translated back, so a model that called a tool reached the client as an empty message — the request half of function calling worked and the response half silently dropped it. Streamed calls are accumulated by `index` (OpenAI splits one call across many deltas, and only the finish chunk proves its arguments are whole) and emitted as a single `functionCall` part with `args` decoded from OpenAI's JSON string into the object Gemini expects. A stream that ends without a `finish_reason` still flushes what it accumulated, malformed arguments fall back to `{}` rather than failing the response, and the empty text part that made SDKs render a blank model turn alongside a tool call is no longer emitted.

# v1.0.188 (2026-08-02)

## Fixes
- **Restoring a backup no longer rewinds this install's schema version**: `importDb()` cleared and repopulated `_meta` from the payload, so restoring a backup taken under an older schema reset `schemaVersion` and replayed migrations on the next boot — harmless only while every migration happens to be idempotent. The same path overwrote `totalRequestsLifetime`, which counts the `usageHistory` a restore deliberately preserves, leaving the counter disagreeing with the history still on disk. `_meta` records install identity rather than config, so a restore now leaves it untouched.
- **`INSERT ... RETURNING` no longer silently applies to one row on the sql.js fallback driver**: `run()` stepped each statement once, which is enough for a plain `INSERT`/`UPDATE` but leaves a `RETURNING` statement partly unapplied, since it yields one row per affected record. The remaining rows were dropped and `changes` undercounted, with no error raised. Statements are now drained.
- **A failed first write no longer leaves the sql.js driver running without its schema**: recovery rewinds memory to the last image that reached disk, but when no file exists yet that image is an empty database — consistent with disk, and stripped of the schema the migration had just created. Every later query then failed with a bare "no such table". The adapter now refuses further work and reports the original write failure.
- **An abandoned stream no longer locks a budgeted API key out**: the slot that serializes budgeted requests was freed only by SSE stream completion, so a client that neither drained nor cancelled its stream pinned it until the process restarted. Slots now carry a short idle deadline that each streamed chunk refreshes, so long-running completions keep their slot while abandoned ones release it.
- **Failed web-shell unlocks no longer lock you out of the dashboard**: dashboard login and terminal unlock are separate credentials but shared one lockout bucket, so wrong terminal-unlock attempts counted toward the dashboard lockout — locking an operator out over a password they never typed. Buckets are now scoped per surface. This affected the default configuration most, where every caller already shares a single bucket.
- **The MITM proxy no longer restarts itself after a deliberate stop or a failed start**: a killed child reports exit code `null` rather than `0`, which read as a crash. A start that had already failed its health check would begin a restart loop behind the error it was raising, and a deliberate stop could bring the proxy straight back up. Exits from a child that a restart had already replaced also cleared the live server's handle and deleted its pid file. Intentional kills are now tracked, and each exit handler only acts for the child it belongs to.

# v1.0.187 (2026-08-02)

## Fixes
- **Provider limits auto-refresh actually refreshes again**: the interval on `/dashboard/usage` was armed by an effect that depended on `refreshAll`, which is rebuilt on every refresh because it closes over `refreshingAll`. Each refresh therefore tore down and recreated the 60-second timer, so the countdown restarted from 60 without ever reaching zero and quota data only updated when refreshed by hand. The callback is now held in a ref so the timers keep a stable identity.
- **Auto-refresh timers no longer leak when the tab is hidden**: the same effect returned early on `document.hidden` without registering a cleanup, while the `visibilitychange` listener wrote new intervals into the same refs. Those intervals outlived the effect and kept polling after unmount. Arming and clearing are now shared helpers, and cleanup is registered on every path.
- **Copy buttons no longer claim success when the copy failed**: `useCopyToClipboard` never awaited the clipboard write, so a rejected `writeText` (denied permission, insecure context such as plain HTTP on a LAN address) still rendered a "copied" tick. `copy()` now resolves to whether the text reached the clipboard, falls back to `execCommand` once, and clears its pending timeout on unmount.
- **A failed copy no longer shuts the server down**: the sidebar's **Copy & Shutdown** update flow trusted that success flag, so when the copy silently failed it still stopped the server — leaving no running dashboard and no install command on the clipboard to bring it back. The shutdown countdown now starts only once the command is actually copied.

# v1.0.186 (2026-08-02)

## Fixes
- **PID files can no longer make the app kill an unrelated process**: stopping the MITM proxy, the tunnel, or the app during an update read a PID from a file and signalled it after only checking that *some* process held that PID. A crash leaves the PID file behind (the cleanup paths only run on a graceful stop) and operating systems recycle PIDs, so once the counter wrapped, those paths signalled whatever process had inherited the number — with `sudo kill -9` in the MITM and updater paths, which could reach a root-owned process. Every PID is now persisted together with a start-time token (`/proc/<pid>/stat` field 22 on Linux, `ps -o lstart=` on macOS, `Process.StartTime` on Windows), and nothing is signalled unless that token still matches the live process. PID files written by earlier versions carry no token and are accepted only when the live process's command line still matches the expected program. Force-kill escalation re-proves identity immediately before each signal, since the wait after `SIGTERM` is itself long enough for a PID to be recycled.
- **MITM status no longer reports a stranger's process as the running proxy**: `startServer()` adopted any live PID found in `.mitm.pid` as "our" server and returned `running: true`, so after a crash and PID reuse the dashboard showed the proxy as up while nothing listened on port 443 — and the next stop signalled that unrelated process. Adoption now requires the same identity proof.

# v1.0.185 (2026-08-02)

## Fixes
- **Restoring a backup no longer deletes all usage history**: `importDb()` cleared every table in the schema, including the three that `exportDb()` deliberately omits to keep backups small (`usageHistory`, `requestDetails`, `quotaCache`). Since the payload carries no rows for them, restoring a config backup permanently wiped all usage and request history. Those tables are now left untouched during a restore.
- **API key budgets are enforced regardless of `requireApiKey`**: the daily/monthly budget check sat inside the `requireApiKey` branch while rate limits and model policy applied to any recognized key, so turning `requireApiKey` off silently disabled spend caps. Budget enforcement is now keyed on the request carrying an API key, matching the other per-key limits.
- **A dropped stream no longer locks a budgeted API key out permanently**: the single-in-flight slot that serializes budgeted requests was released only by SSE stream completion, so a client that neither drained nor cancelled its stream pinned the slot until the process restarted and every later request for that key returned 429. Slots now expire, and a late release cannot free a slot a newer request has since claimed.
- **Web-shell tokens are no longer accepted as dashboard sessions**: `terminal_token` and `auth_token` are signed with the same secret, so a bare signature check let a 4-hour terminal token act as a 24-hour dashboard session. Both readers now require the `authenticated` claim that only dashboard login issues.
- **Dashboard logins survive a browser restart**: the `auth_token` cookie had no `maxAge`, so a still-valid 24-hour session was discarded when the browser closed. The cookie lifetime now matches the token's.
- **The MITM proxy is no longer orphaned on port 443**: the proxy runs as root via `sudo`, so liveness checks from the unprivileged dashboard get `EPERM`, which was treated as "not running". Status checks deleted the PID file of a live process, and stop reported success without the process exiting. `EPERM` now correctly means "alive".
- **MITM runtime server picks up new releases**: the copy was skipped whenever the bundled and installed `server.js` had the same byte size, so a new release of identical length kept running the old bundle. Modification time is now compared as well.

## Improvements
- **Faster writes on the sql.js fallback driver**: every standalone write serialized the entire database twice — once for a rollback copy, once to persist. Recovery now reads back the last persisted file instead, since it already matches memory whenever no write is in flight, halving the per-write cost.

# v1.0.184 (2026-08-01)

## Fixes
- **Reliable Grok CLI device authorization**: the xAI bulk-login state machine now confirms the device code again after Google sign-in, submits xAI authorization exactly once, stops cookie and generic consent handlers on the terminal `Device Authorized` page, and reports a cookie dismissal only after a successful click and confirmed disappearance. Device-token polling now uses xAI's canonical minimal scopes, distinguishes OAuth rejection from a genuine timeout, safely retries transient network/5xx responses, and surfaces redacted upstream diagnostics instead of masking `invalid_grant` and other terminal errors as timeouts.

# v1.0.183 (2026-07-31)

## Fixes
- **Qoder bulk auto-login updated for the new Qoder sign-in shell**: Qoder replaced their device login UI with a React SPA (`/users/sign-in` → `/sso/login/google` → post-auth "Approve Sign in" page on `/device/selectAccounts` with a primary **Continue** button that triggers `GET /device/redirect` to release the device token). The bulk Google automation now recognizes `qoder.com` as a provider page, clicks the SPA's anchor-style "Sign in with Google", and — after Google auth completes — clicks the Approve-Sign-in **Continue** button (3s cooldown, guarded so it never fires before Google login). Verified end-to-end with fresh browser sessions: `selecting_google_login` at ~4.7s → `/sso/callback/google` at 12.6s → `/device/redirect` 200 at 13.5s → `qoder_token_received` at 14.4s. The device-token poll protocol itself is unchanged and still compatible.

# v1.0.182 (2026-07-31)

## Features
- **Grok CLI automation hub**: Grok CLI (`grok-cli`) now has a dedicated panel on `/dashboard/automation` alongside the other account-harvest providers. The panel offers three flows:
  - **Auto Login Bulk** — paste `gmail|password` / `gmail:password` lines; headed CloakBrowser workers run Google SSO on the xAI device login (the automation already fast-paths xAI/Grok pages), confirm the device code, and save the harvested `grok-cli` tokens (access + refresh + subscription tier from `cli-chat-proxy.grok.com`) as connections. Supports the shared worker progress UI, proxy pools, manual assist, resume, and cancel — same engine as Qoder/Antigravity/CodeBuddy. Measured end-to-end: ~21s per account when xAI releases the device grant promptly.
  - **Bulk Token Import** — paste xAI refresh tokens, `access:refresh` pairs, or `access:refresh:email` lines; bare/expired tokens are refreshed immediately against `auth.x.ai` before saving.
  - **Device OAuth Login** — the standard xAI device-code login card.

  The provider detail page's "Open Automation" button now deep-links to the Grok CLI panel.

## Improvements
- **Faster token capture after authorize**: the grok-cli device-grant poll tightens from 5s to 2s once the browser reports the xAI authorize click (distinguished from Google OAuth consent clicks by page URL), cutting authorize→token latency to ~0.2s when xAI releases the grant immediately.
- **Visible xAI token-release wait**: bulk jobs now surface an explicit "waiting for xAI to release the token" step after the browser authorize, instead of an opaque stall. Note: some xAI device grants are held ~60s server-side before the token can be polled — this is upstream behavior and cannot be shortened client-side.
- **Hardier device-code requests**: the bulk flow retries transient network errors and 5xx/429 up to 3 times with backoff, reports the real network failure cause instead of a bare "fetch failed", and hints to lower concurrency when xAI rate-limits the device flow.

# v1.0.181 (2026-07-31)

## Fixes
- **Fixed non-streaming requests to Grok CLI (and Perplexity Agent)**: every non-streaming call to `gcli/grok-4.5*` failed with `502 "Invalid SSE response for non-streaming request"` (or a 30s timeout / `ResponseAborted`) while streaming worked. Root cause: the chat core resolves the provider as the short OAuth alias (`gcli`), but `getProviderConfig`/`getTargetFormat` looked up `PROVIDERS[provider]` without resolving the alias — and there is no `gcli` key in `PROVIDERS` (only `grok-cli`), so the format fell back to `openai`. The forced-SSE→JSON bridge then took the Chat Completions path and fed grok's Responses-API event stream (`response.created` … `response.completed`) to the Chat parser, which found no `choices[]`/`[DONE]` and returned null → 502. The fix resolves the OAuth alias to the canonical provider id inside `getProviderConfig` and adds `gcli`/`pa` to the alias map, so all Responses-API providers route to the correct SSE→JSON converter. Streaming was unaffected because it bypasses this bridge.

# v1.0.180 (2026-07-31)

## Fixes
- **Sanitized invalid `finish_reason` values from upstream providers**: some providers (notably CodeBuddy CN routes) send `"finish_reason": ""` on every streaming content chunk, which is outside the OpenAI enum (`null`, `stop`, `length`, `tool_calls`, `content_filter`, `function_call`) and crashes strict clients (grok CLI, xAI CLI) at deserialization. The passthrough stream now normalizes empty-string `finish_reason` to `null` right after parsing, and the raw-forward fallback string path applies the same sanitization so verbatim-emitted lines are covered too.
- **Completed `response.completed` payloads for the Responses API**: the terminal `response.completed` event previously carried only `id`/`status`/`error`, omitting `model`, `output`, and `usage` — strict Responses API clients rejected it with `missing field 'model'`. The OpenAI→Responses translator now reconstructs the final `output` array (reasoning, message, and function_call items, in emission order) from accumulated stream state, includes the request `model`, and converts usage to the Responses shape (`input_tokens`/`output_tokens` with cached/reasoning details).

# v1.0.179 (2026-07-30)

## Fixes
- **Completed the dashboard frontend hardening sweep**: the remaining verified medium and low-severity findings across every dashboard page are resolved. Async flows now sequence-guard period/filter switches and stop polling in hidden tabs; destructive actions guard against double-submit and report per-row failures; optimistic mutations roll back and surface errors instead of failing silently; OAuth and status polling clean up timers on close so no setState fires after unmount; and stale per-context form, validation, and test state resets across providers, connections, and modal opens. Includes smaller correctness fixes (per-model test errors, TTS blob revocation, combo round-robin strategy migration on rename, debounced session persistence, a working Stop for background tunnel polls) and accessibility/UX polish (associated labels, keyboard-operable sortable headers, guarded date and quota rendering).

# v1.0.178 (2026-07-30)

## Fixes
- **Hardened dashboard frontend against silent failures and state leaks**: the changelog modal now sanitizes rendered markdown with DOMPurify (closing a stored-XSS path); the media-combo test button no longer sticks on "Running..." forever after a failed test (its loading flag resets in a `finally`); optimistic priority swaps, provider toggles, and combo reorders now check `res.ok` and roll back when the server rejects them, so the UI never shows an order or state that was never persisted; OAuth device-code polling uses a per-session id instead of a shared abort flag, so a quick close-and-reopen can no longer leave a zombie poll that fires `onSuccess` for an abandoned flow.
- **Reliable settings and form state**: a shared `fetchJson` helper (throws on `!res.ok` and on non-JSON error pages) is now adopted across dashboard mutations; the provider detail page shows a load-error banner with Retry instead of a false "No connections yet" empty state that invited re-adding existing accounts; sticky-limit and round-robin saves are debounced and read the latest value, ending a per-keystroke GET→PATCH read-modify-write race; Azure/Cloudflare connection fields, validation badges, and auth-modal form state now reset on context change and open, so values no longer leak between connections or providers.

# v1.0.177 (2026-07-30)

## Fixes
- **Accurate default-password login hint**: installers materialize the default `123456` as `INITIAL_PASSWORD`, so the login page now distinguishes that default from a genuinely custom installer password instead of sending fresh installs to the protected environment file unnecessarily.

# v1.0.176 (2026-07-29)

## Fixes
- **Accepted Kiro's mislabeled binary streams**: successful chat responses currently carry `Content-Type: application/json` even though their body is a valid AWS EventStream. The decoder now admits that live media type and relies on structural frame validation, while literal JSON, HTML, empty, and truncated HTTP-200 bodies still fail closed.

# v1.0.175 (2026-07-29)

## Fixes
- **Resilient multi-account Kiro routing**: model discovery now unions cached catalogs and warms a bounded rotating batch instead of trusting the first account. Requests still prefer verified supporters, but when those supporters are locked or have failed they can recover through at most three uncataloged accounts; fresh omissions and `INVALID_MODEL` tombstones remain authoritative negatives so unsupported models are never sprayed across a large pool.

# v1.0.174 (2026-07-29)

## Fixes
- **Self-healing macOS router startup**: `zevai` now identifies and reaps only exact orphaned ZevaiRouter `next-server` listeners when HTTP health/version probes hang, while mixed or unrelated port owners still fail closed. LaunchAgent-owned children stay attached to launchd, and hidden server errors go to a durable private log instead of a parent-owned pipe that can break and spin after an abrupt CLI exit.

# v1.0.173 (2026-07-29)

## Fixes
- **Reliable Kiro routing and account recovery**: policy or credential suspensions now quarantine the affected account without refreshing on 403, rate limits rotate to another eligible account, and transient retries are bounded to one transport owner. Live per-account model catalogs and `INVALID_MODEL` tombstones prevent unsupported-account probing, while malformed or interrupted HTTP-200 streams fail closed and finalize lifecycle bookkeeping exactly once. Rotating refresh tokens are persisted before reuse, protected from cache/invalidation races, deduplicated across concurrent waiters, and never replayed through proxy-to-direct fallback.

# v1.0.172 (2026-07-29)

## Chores
- **Green CI again**: the Quality workflow had been failing on every push for many releases. The database benchmark imported `lowdb`, which is declared only in `tests/package.json` and so is absent from a root `npm ci`; the import error took the whole suite down along with the build, package-inspection, and smoke steps behind it. It now skips when `lowdb` is unavailable, since it is a benchmark rather than a correctness gate. With the suite running to completion again, a latent race in the installer rollback test surfaced and is fixed: it read the restarted server's argument file before the detached process had written it.

# v1.0.171 (2026-07-29)

## Fixes
- **No leaked upstream connections on retry**: every executor retry path now releases the response body it abandons instead of holding its pooled socket until garbage collection. This covered the shared retry loop and fallback-URL advance (affecting all providers), Antigravity's five retry paths, Kiro's own retry loop, twelve token-refresh error paths across seven providers, MiMo's re-bootstrap, and Grok Web's synthesized error responses. A provider flapping 502/503, or a permanently invalid refresh token, previously stranded one connection per attempt.
- **Correct Codex retry cleanup**: the SSE-overloaded retry cancelled a stream that had already been re-locked for buffering, so the cancel silently failed and the socket stayed checked out. It now releases the stream that actually holds the connection.
- **Accurate Antigravity quota locks**: a long quota exhaustion returned through a generic error that discarded the upstream response, losing the reset timestamp it carried. The account was then locked for a short generic backoff and re-picked while still exhausted; it is now locked until the provider-reported reset time.
- **Explicitly declared runtime dependencies**: the chat sanitizer (`dompurify`), CLI colour output (`chalk`), and dashboard prop validation (`prop-types`) were resolved only as incidental sub-dependencies of development tools, so nothing pinned their versions and a dependency prune could remove them. They are now declared directly, with a test that fails on any undeclared import.

## Changes
- **Pricing settings inside the dashboard shell**: `/dashboard/settings/pricing` sat outside the dashboard route group and rendered without the sidebar and navigation. The URL is unchanged.
- **Removed a superseded provider page**: a 1726-line duplicate of the provider detail page that Next never routed to, whose components already exist as individual files, is deleted.

# v1.0.170 (2026-07-29)

## Fixes
- **Respected `ZEVAI_HOST` for service installs**: the generated systemd unit hardcoded `--host 0.0.0.0`, so an explicit or persisted bind host was silently ignored even though the installer validated it and reported it in the summary. Root Linux service installs still default to every interface (remote access is the point of a server install), while desktop, unprivileged, and service-less installs stay on loopback; the install summary now reflects the bind that was actually applied.

# v1.0.169 (2026-07-29)

## Security
- **Authenticated web-shell provisioning**: setting the terminal password now requires a proven dashboard session (or local CLI token), closing a path where an anonymous caller on an instance with login disabled could set the unset first-run password, obtain the unlock cookie, and spawn a shell.
- **Non-forgeable lockout identity**: dashboard and terminal login lockout no longer trust `x-forwarded-for` by default, so rotating that header can no longer allocate a fresh attempt bucket per request and bypass progressive lockout entirely. Deployments behind a reverse proxy that overwrites the header opt in with `TRUST_PROXY_HEADERS=true` (plus `TRUSTED_PROXY_HOPS`) to restore per-client scoping.
- **Verified request locality**: spawn-capable and host-secret routes stop inferring locality from the client-supplied `Host` header. A loopback host is only honoured when the listener is actually bound to loopback; instances reachable off-box require a real dashboard session.
- **Bounded auth and rate-limit state**: the login-attempt, API-key rate-window, and Grok CLI turn-index maps are now capped with expiry-aware eviction, so client-varied identities cannot grow them for the lifetime of the process. Active lockouts are never evicted ahead of idle entries.

## Fixes
- **Preserved Gemini image attachments**: a Gemini message carrying an image alongside a tool call no longer collapses to an undefined text field, so the image reaches the provider instead of being dropped silently.

# v1.0.168 (2026-07-29)

## Fixes
- **Fresh CodeBuddy CN account routing**: zero-quota cache entries expire after bounded background-refresh lag, so newly credited or recovered accounts are probed instead of incorrectly reporting the entire pool unavailable until an old reset timestamp.

# v1.0.167 (2026-07-29)

## Fixes
- **Live-verified CodeBuddy CN phone flow**: automation now selects the visible Keycloak phone iframe instead of hidden bootstrap/logout frames, handles both consent timings, verifies configured proxy exits are in CN/HK, and reliably switches the live country dropdown from `+86` to Hong Kong `+852` before purchasing a number.
- **Safe CodeBuddy CN SMS lifecycle**: real 5sim terminal statuses stop polling immediately, local timeouts cancel refundable orders instead of banning numbers, pre-SMS captchas preserve the browser for manual completion, and OTP values are no longer written to progress logs.
- **Recoverable CodeBuddy CN jobs**: fresh dashboards restore the active server-side singleton job, concurrency is fixed at one to prevent shared-browser cascades, unsupported China inventory is removed, manual follow-ups block worker reuse, and API-key creation requires a recognized authenticated account response.

# v1.0.166 (2026-07-29)

## Fixes
- **Consistent default dashboard password**: local, systemd/VPS, and fresh Docker installs now use `123456` when `INITIAL_PASSWORD` is unset, restoring the original first-login flow while preserving explicit or previously stored values.

# v1.0.165 (2026-07-29)

## Fixes
- **Reliable systemd dashboard login recovery**: first-login auth accepts either the initial password value or its quoted/full `INITIAL_PASSWORD=...` EnvironmentFile form, new installers write copy-safe hexadecimal entries, and the CLI reset now clears the active SQLite password hash before reporting the actual configured fallback.

# v1.0.164 (2026-07-28)

## Fixes
- **CodeBuddy CN full-page phone login**: consent handling now recognizes Tencent's proxy-dependent top-level Keycloak redirect as a valid phone form instead of incorrectly retrying a vanished `手机号` tab and reporting a proxy failure.

# v1.0.163 (2026-07-28)

## Fixes
- **CodeBuddy CN consent re-render recovery**: after accepting Tencent's service agreement, phone login now resolves a fresh `手机号` locator before reopening the iframe, avoiding stale-element stalls on proxy-dependent page variants.

# v1.0.162 (2026-07-28)

## Fixes
- **Live-verified CodeBuddy CN Hong Kong selection**: phone login now handles Tencent's consent dialog after its first tab click, verifies the agreement state, reopens the gated iframe, and then selects and confirms `+852` against the live dropdown.

# v1.0.161 (2026-07-28)

## Fixes
- **Safe CodeBuddy CN phone registration**: automation now uses the verified login route, preserves checked consent state, acknowledges the SMS request before polling, submits OTP once, validates browser-context proxy stability, and gates a single API-key creation request behind confirmed authentication.

# v1.0.160 (2026-07-28)

## Fixes
- **Self-excluding Unix process scans**: installer shutdown now matches only Node processes whose script argument is the selected ZevaiRouter CLI or shim, preventing its short-lived `awk` scanner from perpetually rediscovering itself.

# v1.0.159 (2026-07-28)

## Fixes
- **Bounded macOS process reaping**: after a forced desktop cutover, the installer now allows a verified exiting runtime up to 30 seconds to be reaped before declaring rollback failure.

# v1.0.158 (2026-07-28)

## Fixes
- **Orphan-safe macOS updates**: installer cutover now follows detached Next.js children through graceful and forced shutdown using their process identity and exact package working directory, including npm's temporary rename; rollback also preserves tray/server mode when only the child remains.

# v1.0.157 (2026-07-28)

## Fixes
- **Reliable macOS installer cutover**: desktop updates allow slow Next.js shutdowns to finish, wait after a forced stop, and report an explicit error instead of silently aborting between runtime preparation and LaunchAgent restart.

# v1.0.156 (2026-07-28)

## Fixes
- **Reliable npm release completion**: release verification now tolerates bounded registry propagation delays, and package metadata uses npm's canonical bin and repository forms without publish-time rewrites.
- **Persistent desktop bind address**: tray autostart now records the selected host alongside its port, and the Unix installer accepts an explicit `ZEVAI_HOST=0.0.0.0` for LAN-accessible Mac or desktop installs without weakening the localhost default.

# v1.0.155 (2026-07-28)

## Fixes
- **Transactional one-command installers**: macOS, Linux, VPS, and Windows installs now validate Node, package identity, health, database readiness, and the exact running build, while preserving a verified rollback until the new runtime passes.
- **Complete-download public bootstrap**: Unix and Windows one-paste commands stage the entire installer in a temporary file, require a successful transfer, validate its syntax, and clean it up before returning instead of executing a live network stream.
- **Safer VPS preparation**: piped setup never trusts an `install.sh` from the caller's directory, optional swap creation degrades cleanly on unsupported or low-disk hosts, and the complete Xvfb/Chromium runtime dependency set is verified.
- **Fast side-effect-free CLI commands**: `zevai --help`, `--version`, and other informational commands no longer initialize optional runtimes or touch the network; normal startup prefers built-in `node:sqlite`, and tray repair runs only for tray launches.
- **Published runtime completeness**: packaged `npm start` now selects the generated standalone app correctly, custom tray ports persist across desktop autostart, and local server defaults consistently bind to loopback.

## Security
- **Hardened deployment credentials**: VPS and Docker installs generate persistent first-login secrets and protect them from symlinks, special files, and permissive modes; Docker also runs the application and browser as a non-root user.
- **Strict publish boundary**: npm releases now use an explicit runtime allowlist, reject secret-like files and machine-local build paths, require critical server assets and executable modes, and run the package audit before any publish or release push.
- **Dependency advisory gate**: the remaining development-tool advisory is resolved, the lockfile pins the safe transitive release, and full high-severity npm audits now gate CI and both publish paths.
- **Safer Docker build context**: local agent state, package-manager credentials, runtime data, databases, keys, logs, and generated build trees are excluded from Docker build contexts.

## Improvements
- **Smaller reproducible npm install**: the published package now has five exact-pinned production dependencies instead of shipping build-only UI dependencies, while CloakBrowser, native SQLite, PTY, and tray components remain bounded optional runtimes.
- **Isolated production builds**: normal app builds keep home, data, database, backup, credential, and machine-identity writes inside the disposable CLI build workspace.
- **Single guarded release path**: the nested CLI package is private, release automation rolls Unreleased notes into the versioned changelog, and maintainers cannot bypass the root package audit through nested publish scripts.
- **Production-ready container runtime**: Docker now uses a glibc Debian base with `tini`, Xvfb, Python, Chromium libraries, persistent user home/cache paths, multi-architecture QEMU builds, and documented shared-memory sizing.
- **Verified Docker publishing**: release tags must match a stable package version, manual publishes are main-only, credentials stay step-scoped, and an amd64 candidate must pass health and exact-version checks before multi-architecture images are pushed.
- **Isolated quality gates**: CI runs locked installs, lint, the full test suite, publish-bundle construction, package auditing, and a Docker build; tests use a temporary `DATA_DIR` instead of real user data.

## Documentation
- **Public-repository onboarding**: install, update, Docker, VPS, credential, port, and Node requirements are aligned across all locales, Docker examples include Chromium-safe shared memory, and `CONTRIBUTING.md` documents safe development and validation practices.
- **Sanitized product screenshots**: all README dashboard captures now use fresh synthetic state and contain no real accounts, API keys, tunnel URLs, activity history, or stale release identifiers.

# v1.0.154 (2026-07-27)

## Fixes
- **Protocol-safe cross-provider routing**: Chat, Responses, Claude, and Gemini requests now preserve streaming mode, token limits, reasoning controls, response formats, tool selection, usage, terminal status, and provider-native non-stream envelopes across translation boundaries.
- **Reliable stream failure recovery**: malformed or failed payloads become structured errors, retry responses release superseded bodies, abort and terminal events settle once, and HTTP-200 in-band failures now mark requests failed and put unhealthy accounts into the correct cooldown.
- **Durable quota and database lifecycle**: quota refresh is single-flight and shutdown-aware, pending request timers are isolated, buffered usage writes flush safely, SQL.js rolls back failed persistence, and database retries no longer leave stale maintenance or signal handlers attached.
- **Robust VPS automation**: CodeBuddy CN, Kiro, CloakBrowser, and 5sim flows now use bounded launch, navigation, polling, cleanup, and cancellation paths while preserving pasted-proxy sessions and preventing duplicate purchases or indefinite workers.
- **Correct CLI API-key setup**: Terminal UI, API Keys, MITM, and Quick Setup retrieve raw keys only through an authenticated no-store endpoint instead of writing masked or undefined values into local tool configuration.
- **Strict dashboard APIs**: API key, provider, proxy pool, usage, metrics, and media routes reject malformed input, preserve valid scalar types, handle update races, and return browser-safe response shapes without raw secrets.
- **Safe remote image handling**: Codex and Kiro image prefetching now blocks SSRF and oversized bodies without deadlocking the DNS-pinned dispatcher, leaking sockets, or dropping supported 1 MB image payloads.

## Security
- **Credential-safe diagnostics**: provider errors, automation snapshots, proxy URLs, usage breakdowns, Bearer/Basic headers, query secrets, and plain-text token/password fields are redacted before persistence or browser responses.
- **Patched production dependency floors**: vulnerable transitive `body-parser` and `sharp` releases are overridden with fixed runtime versions; the production dependency audit is clean.

## Improvements
- **Responsive live dashboard behavior**: usage streaming is coalesced and cancellation-safe, async pollers cannot overlap after unmount, media cards use authenticated proxy routes, and cooldown displays stop updating once their deadline expires.

# v1.0.153 (2026-07-27)

## Fixes
- **Legacy Codex lock recovery**: accounts whose health checks already cleared `lastErrorAt` can now use a newer positive quota snapshot to bypass a stale model lock once, restoring accounts that still have usable quota.
- **Durable lock evidence**: new model locks retain their own `lockedAt` timestamp, while successful authentication tests preserve active-lock error metadata instead of silently breaking future recovery decisions.

# v1.0.152 (2026-07-27)

## Fixes
- **Accurate Codex quota recovery**: a newer quota snapshot that explicitly reports capacity can now supersede a stale per-model lock instead of returning a false long-lived 429 while quota remains available.
- **Bounded recovery probes**: recovered accounts are retried once only when the quota snapshot is newer than the locking error; a fresh upstream failure immediately restores the lock and prevents retry loops.

# v1.0.151 (2026-07-27)

## Fixes
- **Deterministic DataImpulse sticky sessions**: CodeBuddy CN now initializes pasted proxies with sequential CN-accessible exit checks, preventing parallel first requests from racing sticky-port assignment and falsely reporting an immediate rotation.
- **Exit-aligned CloakBrowser identity**: the verified residential exit is applied directly to WebRTC, timezone, and locale flags instead of allowing failed foreign echo services to substitute the proxy gateway address.
- **Safe pre-purchase verification**: browser-context checks use the same sequential fallback order and compare the initial exit with the exit immediately before the 5sim purchase, without modifying pasted credentials or Proxy Pool data.

# v1.0.150 (2026-07-27)

## Fixes
- **Coherent pasted-proxy browser identity**: CodeBuddy CN manual-proxy jobs now preserve CloakBrowser's binary-level exit IP, WebRTC, timezone, locale, and native viewport instead of overwriting them from the SMS number region.
- **Safe sticky-egress preflight**: manual proxies are verified against multiple exit-IP services at launch and immediately before the 5sim purchase, so unverifiable, rotating, or changed exits fail before a phone number is ordered.
- **Isolated manual registrations**: pasted-proxy jobs enforce one worker and recycle the complete CloakBrowser process between accounts, including cleanup after unexpected worker failures, without changing Proxy Pool selection or data.

# v1.0.149 (2026-07-27)

## Fixes
- **Accurate CodeBuddy CN restriction detection**: post-OTP sessions that retain Tencent's `login_risk_state` and repeatedly resolve to Keycloak login are now reported as `failed_restricted` instead of a generic API-key capture failure.
- **CORS-independent console probing**: account-session checks prefer the browser context request API, preserving cookies, redirect URLs, response status, and login HTML even when in-page fetch is blocked or collapses to HTTP 0.
- **Safe restricted-account termination**: confirmed restrictions stop OTP resubmission and skip API-key/manual fallback, while summaries count the terminal result correctly and browser resources close immediately.

# v1.0.148 (2026-07-27)

## Fixes
- **Bounded CodeBuddy CN automation**: browser launch, context creation, console navigation, session probes, API-key requests, manual fallback, resource cleanup, and 5sim settlement can no longer hold a VPS worker indefinitely.
- **Safe console-session recovery**: login HTML returned with HTTP 200 is rejected as an unauthenticated session, destructive `/login` re-entry is avoided, and OIDC/API retry budgets are shorter and explicit.
- **Duplicate-key protection**: timed-out or response-less API-key creation is treated as an unknown outcome, stops automatic POST retries, and asks the operator to check existing keys first.
- **CloakBrowser runtime compatibility**: stale runtime installs are detected by semantic version and upgraded to the supported `0.5.2` series before automation starts.

## Improvements
- **Lower VPS browser pressure**: low-memory hosts use bounded viewport/DPR fingerprints and skip trackers, media, and non-captcha imagery while preserving challenge resources.
- **Visible long-request progress**: API-key creation emits a five-second heartbeat so slow CodeBuddy responses remain distinguishable from a frozen job.

# v1.0.147 (2026-07-27)

## Fixes
- **Reliable Playground hydration**: persisted chats, drafts, and settings now restore after deterministic hydration, eliminating direct-load React mismatches and failed model-discovery requests.

## Improvements
- **Focused mobile quota controls**: primary filters stay visible, secondary actions move into a readable disclosure, long quota cards surface the three most urgent limits first, and touch targets meet mobile sizing.
- **Adaptive routing and provider navigation**: mobile Routing Map becomes a readable route-status list, while provider status filters and collapsible sections shorten catalog navigation without changing the desktop workflow.
- **Consistent responsive controls**: Endpoint actions, Tailscale styling, dashboard header controls, and provider toggles now share precise geometry and established visual language.

# v1.0.146 (2026-07-27)

## Improvements
- **Calmer provider scanning**: provider cards now use uniform compact geometry, one clear connection-status line, quieter borders, and less repeated authentication metadata while keeping connection controls and provider capabilities intact.
- **Cleaner usage hierarchy**: overview metrics, Routing Map, and Recent Requests use shorter aligned panels, sentence-case labels, clearer provider names, and restrained semantic color instead of decorative accents.
- **Responsive theme polish**: the provider summary is balanced on mobile, icon containers stay precisely centered, secondary actions respect light and dark themes, and provider/usage layouts remain overflow-free across desktop and mobile.

# v1.0.145 (2026-07-27)

## Documentation
- **Complete recent release history**: backfilled the missing v1.0.139-v1.0.144 notes so the dashboard changelog accurately reflects the Codex compatibility, connection-ordering, UI polish, and usage-accounting changes already shipped.

# v1.0.144 (2026-07-27)

## Fixes
- **Accurate streaming usage accounting**: streaming completions with the richer completion callback are now persisted exactly once instead of writing duplicate request, token, and cost metrics. Legacy stream callers without that callback keep their original persistence path.
- **Safe recent-request deduplication**: legacy double-writes are collapsed only when the full timestamp, provider, model, and token counts match. Distinct requests with identical tokens in the same minute are no longer hidden.

## Improvements
- **More informative Recent Requests table**: each row now shows its provider, accessible success/failure status, exact timestamp tooltip, and live SSE connection state. The desktop panel is wider while retaining a no-overflow mobile layout.

# v1.0.143 (2026-07-27)

## Improvements
- **Dashboard visual polish**: refined the mobile header and navigation, desktop sidebar, overview cards, routing map, usage chart, Endpoint page, and Providers page for clearer hierarchy and denser but more readable layouts.
- **Consistent component geometry**: centered Material Symbols inside their icon boxes, replaced the tilted Endpoint glyph, aligned provider health indicators, and normalized provider-card heights and spacing across responsive grids.

# v1.0.142 (2026-07-26)

## Fixes
- **Deterministic provider connection ordering**: explicit priority moves now land at the requested position, ties use stable IDs instead of update timestamps, and create/update responses return the normalized stored priority.
- **Stricter Codex hosted-tool validation**: malformed hosted-tool shapes are rejected before forwarding while supported custom and hosted tools retain the schema Codex expects.

# v1.0.141 (2026-07-26)

## Fixes
- **Codex named hosted-tool rejection**: built-in hosted tools are accepted only when the `name` property is absent, preventing ambiguous payloads from reaching the Codex upstream API.

# v1.0.140 (2026-07-26)

## Fixes
- **Codex hosted-tool schema validation**: only supported hosted-tool types are forwarded, and custom tools must provide a non-empty name.

# v1.0.139 (2026-07-26)

## Fixes
- **Codex CLI tool preservation**: Codex clients are detected before the generic CLI fallback, including Codex originator headers, and valid named custom tools are no longer stripped during normalization.

# v1.0.138 (2026-07-26)

## Security
- **Next.js 16.2.12**: bump `next` (root + gitbook) to clear Dependabot advisories for Middleware bypass, DoS, SSRF, XSS, and related App Router issues below 16.2.6.

# v1.0.137 (2026-07-26)

## Improvements
- **Smarter one-liner install**: `install.sh` / `vps-setup.sh` auto-detect and install missing Node (when possible), curl/ca-certificates, Xvfb, CloakBrowser runtime (not stock Chromium/Camoufox), and start the service — paste-and-enter friendly.

# v1.0.136 (2026-07-26)

## Improvements
- **Lighter Automation runtime cleanup**: removed the unused `playwrightRuntime` helper and full `playwright` package wiring from postinstall/build/standalone. Only CloakBrowser + `playwright-core` peer remain.

# v1.0.135 (2026-07-26)

## Breaking
- **CloakBrowser-only Automation**: stock Playwright Chromium is removed as a bulk-import engine. All providers (Kiro, CodeBuddy, CodeBuddy CN, Antigravity, Qoder, Cloudflare AI) launch headed CloakBrowser. Postinstall warms CloakBrowser + playwright-core peer (no stock Chromium browser download).

# v1.0.134 (2026-07-26)

## Improvements
- **Cloudflare AI Turnstile via CloakBrowser**: Cloudflare Workers AI bulk import defaults to headed CloakBrowser again (auto-install into `~/.zevai/runtime`) so Cloudflare Turnstile / bot checks that block plain Playwright Chromium can pass. (Full Cloak-only for all providers landed in **1.0.135**.)

# v1.0.133 (2026-07-26)

## Improvements
- **CodeBuddy CN per-account Chromium fingerprint**: each bulk-import account now gets a seeded fingerprint (real desktop viewport sizes, deviceScaleFactor, hardwareConcurrency, deviceMemory, and screen metrics) so sticky residential IPs no longer share the same 900×700 + identical navigator.* cluster that Tencent risk scoring can ban after OTP.

# v1.0.128 (2026-07-25)

## Improvements
- **Single Chromium runtime**: removed the obsolete Camoufox and CloakBrowser launchers, runtime installers, engine request fields, and persisted engine state so all supported Automation providers use Playwright Chromium only.
- **Smaller package**: removed the retired Grok CLI and AutoClaw Automation API routes, managers, and tests from the published package.

# v1.0.127 (2026-07-25)

## Fixes
- **Chromium-only Automation**: CodeBuddy CN now uses Chromium exclusively and no longer exposes or defaults to the removed Camoufox browser engine.

# v1.0.126 (2026-07-25)

## Fixes
- **Automation provider menu**: removed Grok CLI and AutoClaw from the Automation dashboard as originally requested, leaving the six supported Automation providers with clearer mode labels and provider descriptions.

# v1.0.125 (2026-07-25)

## Improvements
- **Antigravity Gemini 3.6 Flash**: added Low, Medium, and High reasoning variants to model selection and quota visibility.
- **Accurate Antigravity tiers and resets**: subscription metadata is refreshed even when a project ID is saved, while provider-supplied reset timestamps remain authoritative for free and paid accounts.

## Fixes
- **Automation completion flow**: Done & Refresh now clears completed state, closes the modal, refreshes connections, and no longer restores terminal jobs. Removed a stale browser-engine state setter that crashed the click handler.

# v1.0.124 (2026-07-25)

## Improvements
- **Smaller Automation installation**: npm postinstall now prepares only Playwright Chromium and no longer downloads unused Camoufox or CloakBrowser runtimes, reducing installation time and disk usage.
- **Chromium-only build graph**: Automation loads the Playwright runtime directly so legacy browser hooks and packages are excluded from production bundles.

# v1.0.123 (2026-07-25)

## Improvements
- **Lighter Automation browser runtime**: all Automation providers now use Chromium consistently, with browser-engine selectors removed to prevent unsupported or stale engine choices.
- **Focused Automation menu**: Grok CLI and AutoClaw are hidden from the Automation dashboard while their backend integrations remain available.

## Fixes
- **Authenticated SOCKS5 Automation**: Chromium browser and context proxy paths now use a local HTTP CONNECT bridge for SOCKS5 proxies with credentials, including safe concurrent reuse, credential-isolated caching, fail-closed startup, and cleanup on partial browser/context failures.

# v1.0.120 (2026-07-24)

## Improvements
- **Dashboard UX and accessibility**: improved responsive layouts, loading/error/retry feedback, keyboard and screen-reader semantics, modal focus management, and light/dark consistency across Playground, Endpoint, Console Log, and Terminal workflows.
- **Refined ZevaiRouter identity**: polished the existing Z mark across the dashboard, favicon, PWA icons, and desktop tray assets.

## Security
- **Safe Playground Markdown**: assistant-rendered Markdown is sanitized before insertion into the page.

# v1.0.119 (2026-07-24)

## Security
- **API-key hardening**: stored secrets are masked in dashboard/API responses, trusted server consumers must explicitly request them, and newly created or rotated secrets are shown only once.
- **Per-key policy enforcement**: API keys can restrict allowed models, requests per minute, and daily/monthly budgets. Budget accounting fails closed and concurrent budgeted requests are serialized to prevent limit bypasses.

## Fixes
- **Request lifecycle safety**: client disconnects now cancel upstream provider work, discarded retry bodies are closed, SSE admission slots release on completion/error/cancellation, and combo routing keeps the final error paired with its actual HTTP status.
- **Durable shutdown and sql.js ownership**: signal shutdown flushes pending request details, stops quota timers, and serializes database closure. sql.js now prevents concurrent process ownership and safely reclaims stale PID locks.
- **Provider alerts**: provider-down and quota alerts now fire on the canonical account cooldown transition instead of once per failed request, avoiding notification spam.

## Features
- **Operational probes**: added `/api/health/ready` for database/provider readiness and authenticated `/api/metrics` Prometheus output for traffic, tokens, cost, active work, provider connections, and cache statistics.

# v1.0.118 (2026-07-24)

## Fixes
- **Automation job serialization**: bulk-import providers now reject overlapping active jobs, including interrupted jobs restored from persisted state, with a structured `BULK_IMPORT_ACTIVE` response.

# v1.0.117 (2026-07-24)

## Fixes
- **Streaming and fallback robustness**: SSE parsing now handles arbitrary chunk boundaries, split UTF-8 characters, and final unterminated events. Provider/account fallback uses accurate retry timestamps and immediately fails over on gateway/service failures.
- **Upstream lifecycle**: streaming inactivity timeouts cancel stalled upstream work, token counting uses structured events, and request validation rejects malformed input before provider execution.
- **Dashboard reliability**: CLI-tool status detection now reports installed and running tools consistently.

# v1.0.116 (2026-07-24)

## Security
- **Outbound request hardening**: URL validation blocks private and special-address targets, pins resolved DNS addresses to prevent rebinding, rejects redirects, and enforces response-size limits.

## Fixes
- **Data directory migration safety**: explicit `DATA_DIR` configurations no longer trigger ambiguous or unsafe automatic migration behavior.

# v1.0.115 (2026-07-23)

## Chores
- **Release flow**: added a safe ordered release command so verification, packaging, version synchronization, commit, push, and publish happen in a predictable sequence.

# v1.0.114 (2026-07-23)

## Fixes
- **Grok automation cancellation cleanup**: cancelling an account during manual assistance now force-closes its independently owned browser before clearing the session, preventing orphaned browser processes when graceful shutdown hangs.

# v1.0.113 (2026-07-23)

## Fixes
- **Full-code audit — security, durability, cancellation, and streaming fixes**: closes verified auth bypasses and credential leaks, hardens OIDC against SSRF and DNS rebinding, isolates terminal sessions per owner, makes SQLite backups/imports and sql.js writes durable and transactional, stops retries and MITM requests after cancellation, and rejects malformed or truncated SSE responses.

# v1.0.112 (2026-07-21)

## Features
- **CodeBuddy CN bulk import — manual proxy URL**: the automation previously only accepted proxies from DB proxy pools (`proxyPoolMode` + `proxyPoolId`). The register modal now has a "Manual Proxy URL" field — paste any `http(s)://` or `socks4/5://` proxy (e.g. a sticky residential URL you haven't added to a pool) and it takes precedence over pool selection (dropdown auto-disables). The manager validates the scheme, marks the job `proxyPoolMode: "manual"`, and every account in the job runs through that proxy.

# v1.0.111 (2026-07-21)

## Fixes
- **CodeBuddy CN bulk import — SPA "did not boot" + API key 401** (re-release): v1.0.110 was published before these changes were committed, so the live build never contained them. This release ships them. (1) Tencent's privacy-consent dialog (a `同意` button) gates the login UI depending on the egress IP — the automation now waits up to 6s for the consent button and dismisses it (logs `cn_consent_dismissed`) before the normal SPA-ready wait, instead of timing out with `html=2670`. (2) API key creation now retries up to 3 rounds on 401/403 (openresty edge rejecting half-synced Keycloak cookies), nudging the OIDC re-handoff and waiting for a strong Keycloak session with 8s/16s/24s backoff.

# v1.0.110 (2026-07-21)

## Fixes
- **CLI Tools: OpenCode page crash ("This page couldn't load")**: `OpenCodeToolCard` line 72 had a broken optional chain — `status?.config?.agent?.explorer?.model?.startsWith("zevai/") || status.config?.agent...` — when `status` was `null` (tool not installed), the second operand `status.config` threw `TypeError: Cannot read properties of null (reading 'config')` during render, crashing the entire page. Fixed by extracting `explorerModel` with a full optional chain and using it for both the condition and the value. Also added `isOpen` guard to `ModelSelectModal.groupedModels` useMemo to skip heavy per-provider computation when the modal is closed (prevents hydration lag with many connections). Verified with headless-Chrome repro: page renders clean, zero pageerror.
- **CodeBuddy CN bulk import — SPA "did not boot" + API key 401**: two compounding fixes in the phone-automation flow. (1) Tencent's privacy-consent dialog (a `同意` button) gates the login UI depending on the egress IP — the automation waited for the SPA to be "ready" while the consent modal blocked it, so it timed out with `html=2670`. The flow now waits up to 6s for the consent button, dismisses it (logs `cn_consent_dismissed`), then proceeds with the normal SPA-ready wait. (2) When API key creation returned 401/403 (openresty edge rejecting half-synced Keycloak cookies, common on slow proxies), the flow now retries up to 3 rounds — nudging the OIDC re-handoff and waiting for a strong Keycloak session with 8s/16s/24s backoff — instead of failing on the first attempt. Error messages now surface the edge-proxy-blocked case explicitly.

# v1.0.109 (2026-07-21)

## Fixes
- **`zevai update` OOM on small VPS (≤2GB)**: service-manager detection now runs **before** install, and supervised non-root setups (cron watchdog / pm2) stop the old server first — freeing ~600MB so `npm install` completes instead of getting OOM-killed (previously exit 137 with an empty output). Root+systemd and desktop/tray are unchanged. Verified live on a 2GB cron VPS: install completed in 21s after the pre-stop, watchdog relaunched the new build, version consistent.

# v1.0.108 (2026-07-21)

## Fixes
- **Antigravity: stop re-picking quota-dead accounts** — three compounding bugs caused ~30s stalls then a 429 whenever the router picked an account with exhausted individual quota, even with 100+ healthy accounts idle:
  1. `parseRetryFromErrorMessage` only matched `"reset after XhYmZs"` — Google actually sends `"Resets in 158h12m52s"`, so `retryMs` was null and the executor ran its full 3x exponential retry loop (twice) against an account that had stated a 6-day recovery.
  2. `parseError` never extracted the reset time from `google.rpc` `ErrorInfo.quotaResetTimeStamp` / `RetryInfo.retryDelay`, so the auth layer fell back to a generic short backoff.
  3. `MAX_RATE_LIMIT_COOLDOWN_MS` capped provider-reported locks at 30min, so 6-day exhaustions re-picked the dead account every half hour.
  
  Now: the parser matches all Google phrasings, the executor extracts `resetsAtMs` from the RPC details (account locks until the **real** reset, same as Codex), futile retries on long quota errors are skipped in favor of immediate failover, and the cooldown cap is 7 days (it only applies to provider-reported timestamps, never guessed backoffs). Includes 10 regression tests built from a production 429 body.

# v1.0.107 (2026-07-20)

## Fixes
- **Full-code audit — 6 verified bugs fixed** (multi-zone static scan + adversarial verification; every finding traced to a concrete failure path, guesses discarded):
  - **Usage recent-logs always empty**: `getRecentLogs` called the async `getAdapter()` without `await`, so `db.all()` threw on a Promise and the try/catch silently returned `[]`. Both `/api/usage/logs` and `/api/usage/request-logs` now return rows (verified E2E).
  - **Cloudflare Tunnel dies silently with no auto-recovery**: `killCloudflared` set `intentionalKill = true` even when no live child existed (fresh start / dead stale PID). The flag leaked across the respawn, so the next child's unexpected exit was swallowed and the watchdog respawn never fired. Flag is now armed only when a child/PID was actually killed (reproduced deterministically).
  - **MITM sudo password corruption (non-ASCII)**: `decryptPassword` concatenated a `decipher.update()` Buffer with a `final("utf8")` string, mangling multi-byte UTF-8 at the chunk boundary. Now decodes once via `Buffer.concat`.
  - **Model alias direction inverted in `/api/models`**: both the GET reader and PUT writer treated the KV map as `{model: alias}` when it's actually `{alias: "provider/model"}` (confirmed against the live DB). Fixed both directions.
  - **Kiro MITM dropped reasoning**: `extractThinking` returned only the first `<think>` block per chunk, silently losing every block after the first. Now merges all blocks.
  - **OpenCode settings showed `zevai/x` instead of `x`**: `activeModel` stripped a `^zevairouter/` prefix that is never written (the actual prefix is `zevai/`), so the replace was a no-op.

# v1.0.106 (2026-07-20)

## Fixes
- **Usage page crash ("This page couldn't load")**: the v1.0.105 lint refactor renamed the `fitOpts` object to a module-level `FIT_OPTS` constant in `ProviderTopology.js` but missed the `fitViewOptions={fitOpts}` JSX prop, throwing `ReferenceError: fitOpts is not defined` on the usage dashboard. Fixed the remaining reference. Verified with a headless-browser repro of the crash.

# v1.0.105 (2026-07-20)

## Security
- **Zero `npm audit` vulnerabilities**: force `dompurify@^3.4.12` (monaco-editor@0.55.1 pinned vulnerable 3.2.7 — 16 mutation-XSS advisories, all fixed in 3.4.x) and `postcss@^8.5.10`→8.5.20 (next nested 8.4.31 had the unescaped `</style>` XSS) via `overrides`. Both are transitive-only deps we don't call directly; build verified clean. Remove the overrides once monaco ships a stable with the fix (currently only in 0.56.0-dev) and next bundles postcss ≥8.5.10.

## Fixes
- **Real bugs from a full ESLint pass** (223 → 0 messages):
  - OAuthModal stale closure: `exchangeTokens` useCallback was missing `oauthMeta` dep, so token exchange could send a stale/absent `meta`.
  - Providers page: an effect reset a user-customized `baseUrl` back to the default on every `apiType` change — now bails out when the URL was customized.
  - `Date.now()` called during render (provider cooldown) moved into state; ProviderIcon prop-sync effect replaced with the render-time prev-prop comparison pattern; basic-chat empty-messages array stabilized (was churning the scroll effect every render).
  - `import/no-anonymous-default-export` across 23 provider handler/util modules now use named exports.

## Chores
- **React Compiler strict-mode pass**: triaged all `set-state-in-effect`, `immutability`, `purity`, `refs`, and `exhaustive-deps` reports. Genuine anti-patterns refactored (above); intentional patterns (sync-from-props hybrids, fetch-on-mount, mount-init from localStorage/window, pagination resets, TDZ false-positives on deferred fetch helpers) carry per-line justified `eslint-disable` comments explaining the pattern. No behavior changes; 88 files touched, `node --check` + full production build pass.

# v1.0.104 (2026-07-20)

## Fixes
- **CLI build hang / 100% CPU on publish**: stale `cli/app` and `.next/standalone` from previous builds were being traced into new standalone output, producing recursive `cli/app/cli/app/...` nesting (package grew 88M→155M, `npm pack` hung for 20+ min). `build-cli.js` now cleans both before every build. Package back to ~82M, publish in seconds.
- **Changelog modal 404**: repo is private so `raw.githubusercontent.com` 404s. Changelog is now served from the bundled `CHANGELOG.md` via `/api/changelog` (works offline), with GitHub raw as fallback only.

# v1.0.103 (2026-07-20)

## Features
- **Lowest-latency routing**: new `lowest-latency` account selection strategy. Router tracks per-connection latency (rolling 5-min window, min 2 samples) and picks the fastest healthy account; unknown-latency accounts rank after known ones.
- **Response cache**: opt-out exact-match in-memory LRU (500 entries, 10-min TTL) keyed by SHA-256 of canonical request JSON. Skips streaming, tool calls, and `x-zevai-no-cache` requests. Cached responses return `X-ZevAI-Cache: HIT`. Stats/clear at `GET|DELETE /api/cache/stats`; toggle in Profile.
- **Per-key budget limits**: set Daily/Monthly spend budgets per API key (Endpoint page). Requests over budget are rejected with HTTP 402 before hitting providers. Fail-open on usage-store errors.
- **Retry with exponential backoff**: failed upstream requests (408/429/5xx, network errors — never AbortError) retry up to 2× with 800ms base backoff, configurable per executor.
- **Webhook alerts**: Discord (embeds), Telegram (Bot API), and generic JSON webhooks for provider-down, quota-exhausted, and error-spike events. Per-key throttled, fire-and-forget with 8s timeout. Configure in Profile.

# v1.0.102 (2026-07-20)

## Features
- **Cached tokens in Usage dashboard**: new "Cached Tokens" overview card (with cache hit-rate subtitle) and a "Cached" column in the tokens table. Tracks OpenAI `cached_tokens` / `prompt_tokens_details.cached_tokens` and Anthropic `cache_read_input_tokens` / `cache_creation_input_tokens`, aggregated into daily summaries and per-provider stats.

## Fixes
- **`zevai update` across environments**: cron-watchdog (non-root) installs now kill old processes via a pgrep loop that excludes the CLI's own PID (previously `pkill -f` matched the SSH command string and killed the session). pm2 detection uses strict `^zevai(router)?$` name matching on parsed `pm2 jlist` (was substring-matching unrelated processes like `zevadlmm` on macOS); removed dangerous `pm2 restart all` fallback. Fallback spawn now frees port 1997 first.
- **Cached token fields dropped**: usage normalization was silently discarding `cache_read_input_tokens` etc. before storage — cache stats would always show 0. Cache fields are now preserved end-to-end, and request latency (TTFT + total) is stored in usage meta for the latency router.

# v1.0.90 (2026-07-19)

## Fixes
- **Cloudflare bulk no fixed sleeps**: harvest loop was `wait(1200)` every tick; now wait-for-dashboard URL + short error backoff only. Google path treats Cloudflare like Grok (fast bulk timings).

# v1.0.89 (2026-07-19)

## Features
- **Cloudflare Workers AI bulk automation**: Automation → Cloudflare Workers AI (and **Open Automation** on provider page). Paste `email:password` / `email|password` → Google login on dash.cloudflare.com → create Workers AI API token → save `cloudflare-ai` connection (`apiKey` + `accountId`). Same bulk job UI as Kiro/AG (workers, proxy pool, manual assist).

# v1.0.88 (2026-07-19)

## Fixes
- **Grok CLI quota false 0%**: billing API returns `onDemandCap: 0` when on-demand spend is *disabled*, not exhausted. Parser used to invent On-demand used=1/total=1 (red 0%) while chat still worked. Now skip zero caps; show **Included** (unlimited/100%) when `hasGrokCodeAccess` / unified billing. Unit + live re-fetch verified.

# v1.0.87 (2026-07-19)

## Fixes
- **Provider detail "Open Automation"**: only Kiro/CodeBuddy showed the button under connections pagination. Now all bulk automation providers (Kiro, CodeBuddy, CodeBuddy CN, Qoder, Antigravity, AutoClaw, Grok CLI) get **Open Automation** → `/dashboard/automation?provider=…`. Dual-auth providers keep OAuth/API key alongside.

# v1.0.86 (2026-07-19)

## Fixes
- **Grok bulk `i.split is not a function`**: after device-code Continue, code called `.split()` on `GOOGLE_LOGIN_BUTTON_SELECTORS` (a string array). Use `.slice().join()` instead. Covered by unit regression + smoke.

# v1.0.85 (2026-07-19)

## Fixes
- **Grok/Google bulk no more blind sleeps**: replace multi-second `waitForTimeout` with event-driven waits (URL change, email/password field, Login with Google, success poll). Token poll starts at 800ms. Fast networks no longer sit idle 1–2s after every click.

# v1.0.84 (2026-07-19)

## Features
- **Grok CLI bulk Google OAuth (default)**: paste `email:password` / `email|password` (Gmail/GSuite) → device code → xAI **Continue** (user code) → **Login with Google** → authorize → save connection. Prefer this over tempmail+Turnstile. Prefixes: `google:` / `gmail:` (same), `email-login:` (legacy xAI email+OTP), `auto` / `auto:N` / bare `N` (tempmail register). Faster fill, device-code confirm, no credential typing on provider shells.

## Fixes
- **xAI OTP**: extract SpaceXAI / confirmation-code subjects; tempmail direct fetch improvements for Grok email path.

# v1.0.83 (2026-07-19)

## Fixes
- **Interactive `zevai` Killed while systemd already running**: bare `zevai` used to kill the healthy service and spawn a second next-server; on small VPS that OOM-kills the CLI. Now if `:1997/api/health` is OK, **attach** (print URL / open dashboard / menu) instead of restart. Use `zevai --force-restart` (stops systemd first) when you really want a new process.

# v1.0.82 (2026-07-19)

## Fixes
- **VPS/CLI "Killed" after install**: systemd unit used `MemoryMax=1400M` while the server spawned with `--max-old-space-size=6144` (6GB), so cgroup/kernel OOM-killed the process immediately. Default heap is now **1536MB** (`ZEVAI_MAX_OLD_SPACE_SIZE` to override); install.sh unit uses `MemoryMax=2G` + matching env. Public install gist updated.

# v1.0.81 (2026-07-17)

## Fixes
- **Antigravity bulk CAPTCHA / Google block**: AG bulk launcher used **headless Chromium by default** (Kiro was already headed). Google OAuth rejects headless → CAPTCHA / “browser may not be secure”. Now default **headed** (Xvfb on Linux), same as Kiro. Override: `ZEVAI_ANTIGRAVITY_HEADLESS=1`.

# v1.0.80 (2026-07-17)

## Fixes
- **Kiro bulk Google CAPTCHA regression**: stop aborting Google tracker domains (reCAPTCHA/login beacons); fix Chromium stealth so WebGL/UA match OS (Linux VPS no longer spoofs Mac Intel Iris). Clearer CAPTCHA `needs_manual` message. High concurrency alone was not the issue — fingerprint mismatch was.

# v1.0.79 (2026-07-17)

## Features
- **Install auto-restart**: `install.sh` / `install.ps1` (and public gist) stop the old `zevairouter` process after upgrade and relaunch tray/server (or systemd on Linux root), then health-check `:1997`. `zevai --fix-install` also relaunches detached. Skip with `ZEVAI_SKIP_RESTART=1`.

# v1.0.78 (2026-07-17)

## Features
- **Kiro client fingerprint bump**: present current IDE/CLI versions to Kiro APIs — `KiroIDE-1.0.138`, `kiro-cli/2.12.2` (token refresh, ListAvailableModels UA, generate headers, usage GET). Shared constants in `kiroConstants.js`.

# v1.0.77 (2026-07-17)

## Fixes
- **Antigravity quota UI spam**: revert catalog/quota list to the stable **9 models**. Extra cloudcode ids (`gemini-2.5-flash-*`, `gemini-3.1-flash-lite/image`) map to the same display name / pool and duplicated rows (e.g. multiple "Gemini 3.1 Flash Lite"). Fingerprint bump from 1.0.76 kept.

# v1.0.76 (2026-07-17)

## Features
- **Antigravity fingerprint**: desktop UA bumped to Antigravity/1.125.0 (Electron 39.2.7); MITM IDE version 1.28.0. (Extra model catalog entries reverted in 1.0.77 — quota UI duplicates.)

# v1.0.75 (2026-07-17)

## Fixes
- **Antigravity bulk automation crash/hang**: OAuth `postExchange` used a fragile relative dynamic import of `open-sse` (can fail under Next webpack) and could stall bulk on project resolve. Import via `open-sse/services/projectId.js`, 20s resolve timeout, bulk save still bootstraps projectId without hard-fail.

# v1.0.74 (2026-07-17)

## Fixes
- **Antigravity GSuite/Workspace models + bulk**: bulk/OAuth left `projectId` empty for DASHER accounts, then chat invented random project ids → HTTP 403. Now resolve free-tier managed project when available; GSuite bulk saves top-level `projectId` via peer/bootstrap managed Cloud Code id (no GCP console required); chat reuses peer project before generating; stop treating empty GSuite project as hard unavailable.

# v1.0.73 (2026-07-17)

## Features
- **Cross-device install auto-detect**: `install.sh` (macOS/Linux) + `install.ps1` (Windows) pick the active Node, install with ENOTEMPTY recovery, and on Linux write systemd with absolute Node+zevai paths (no nvm/`/usr` desync). CLI: `zevai --doctor`, `zevai --fix-install`, in-app update uses the same safe installer.

# v1.0.72 (2026-07-17)

## Fixes
- **Kiro bulk Google OAuth (`connection_saved`)**: after password Google parks on empty SetSID while network completes consent → Cognito idpresponse → `kiro://`. Capture callback via CDP Network + page/context listeners (custom-protocol Location never becomes a page nav). Password/email resubmit cooldown; tracker-only `page.route` (no `**/*` continue — that broke the OAuth chain). No aggressive force-goto of idpresponse/consent.

# v1.0.70 (2026-07-17)

## Fixes
- **Kiro needs_manual root cause**: Google rejects **headless Chromium** after email submit (`/v3/signin/rejected` — "This browser or app may not be secure"). Default Kiro bulk now launches **headed Chromium** (Xvfb on Linux). Fail-fast on rejected/not-secure pages instead of 90s of `waiting_for_next_screen` then opaque needs_manual. Richer stuck-state logs (url/body).

# v1.0.69 (2026-07-17)

## Fixes
- **Kiro Cancel stuck on needs_manual**: Cancel only closed the browser and left `needs_manual` accounts waiting on a dead callback (UI never left "running"). Now Cancel immediately finalizes queued/running/needs_manual accounts, closes per-account contexts, force-kills the browser, and marks the job cancelled. Callback monitor rejects on page/context close so followups do not hang 15 minutes.

# v1.0.68 (2026-07-17)

## Fixes
- **Kiro bulk mass-fail on VPS**: Playwright was present under `~/.zevai/runtime` but webpack-bundled `require("playwright")` still threw → every account failed with "playwright installed but cannot be required". Resolve via `createRequire` / `__non_webpack_require__` (same pattern as Camoufox). Prefer runtime module on Chromium launch; never force Google Chrome channel (`/opt/google/chrome`).

# v1.0.67 (2026-07-16)

## Fixes
- **Dashboard false “update available” after npm upgrade**: `/api/version` now reads the installed package version from disk at request time (not only build-time), forces `Cache-Control: no-store`, and clears the sidebar banner when `hasUpdate` is false. Fixes CLI showing 1.0.x while the UI still nags to update after `npm i -g zevairouter@latest` + restart.

# v1.0.66 (2026-07-16)

## Fixes
- **CodeBuddy CN clearer form-load errors**: instead of opaque `proxy/SPA`, report whether it was empty proxy page, nav timeout, SPA timeout, or missing phone iframe — plus a short fix tip (CN proxy / concurrency 1 / 5sim not charged yet).

# v1.0.65 (2026-07-16)

## Fixes
- **Camoufox binary auto-sync (Mac + Linux/Ubuntu + Windows)**: on first use, if the cached browser is missing/stale or `camoufox-js` was upgraded, Zevai runs `camoufox-js fetch` automatically (no manual step). Stamp file tracks package/policy so users do not keep a broken `beta.24` cache forever.
- **Fingerprint config sanitization**: strip unsupported keys (`audio:seed` / `audio:*`) from `CAMOU_CONFIG` before launch; on `Unknown property … in config`, strip that key and retry; if still broken, force-refresh the binary once and relaunch.
- **Offline-safe**: if fetch fails but an old binary exists, launch still proceeds with sanitization instead of hard-failing.

# v1.0.64 (2026-07-16)

## Fixes
- **CodeBuddy CN cancel left Camoufox running**: CN `cancelJob` only aborted 5sim polls and never closed the browser — stuck nav pinned CPU until reboot. Now force-closes browser (graceful close raced with process SIGKILL).
- **Hard account timeout**: per-account wall-clock (~12 min, `ZEVAI_CN_ACCOUNT_TIMEOUT_MS`) force-kills browser if Playwright timeouts do not fire.
- **Low-mem VPS**: CN concurrency hard-capped at 1 when host RAM &lt; 3GB; default concurrency is 1.
- **Faster fail on dead nav**: 2 attempts, 30s goto / 25s SPA, cancel checks between steps.
- **Country dropdown hang**: selectCountry no longer scans 200+ Keycloak options (was ~80s+ on retry); uses `:has-text` + 10s budget.
- **账号访问受限**: fail faster with clear “use residential CN proxy” message instead of thrashing 3 full re-enters.

# v1.0.63 (2026-07-16)

## Fixes
- **CodeBuddy CN / Camoufox on VPS**: auto-start Xvfb for headed Camoufox/Chromium (not only CloakBrowser).
- **Camoufox newContext**: always use `viewport: null` for firefox so Playwright does not send `isMobile` (fixes headed bulk crash).

# v1.0.62 (2026-07-15)

## Fixes
- **CloakBrowser geoip**: auto-install `mmdb-lib` into `~/.zevai/runtime` with cloakbrowser (required for `geoip: true` proxy timezone/locale matching). No manual `npm install mmdb-lib` needed.

# v1.0.61 (2026-07-15)

## Fixes
- **Grok bulk ignored proxy selection**: API did not forward `proxyPoolMode` / `proxyPoolId` from the Automation UI, so traffic still left the VPS IP. Now proxies are applied; CloakBrowser launches with `proxy` + `geoip: true` (timezone/locale match) and per-account browsers when pool URLs differ.

# v1.0.60 (2026-07-15)

## Fixes
- **Grok bulk device authorize**: more aggressive Allow/Authorize UI loop (full poll window), re-login on device page, Turnstile on consent, better logging. Verified end-to-end: tempmail signup → OTP → Turnstile → device OAuth → connection saved.

# v1.0.59 (2026-07-15)

## Fixes
- **Grok bulk on macOS**: headed CloakBrowser no longer blocked when `$DISPLAY` is empty (common for tray/daemon). Desktop OS (darwin/win32) always treated as having a UI; Xvfb only on Linux VPS.

# v1.0.58 (2026-07-15)

## Fixes
- **Grok/CloakBrowser on headless VPS**: auto-start **Xvfb** when headed browser is required but `$DISPLAY` is missing. Clear error if `xvfb` is not installed (`apt-get install -y xvfb`). VPS one-click setup now installs `xvfb` when missing.

# v1.0.57 (2026-07-15)

## Changes
- **Full-feature postinstall warm-up**: after 
added 30 packages, removed 89 packages, and changed 242 packages in 12s

88 packages are looking for funding
  run `npm fund` for details, postinstall quietly installs SQLite, Playwright/Chromium, Camoufox, CloakBrowser, and node-pty into `~/.zevai/runtime` so one install command prepares all features — without re-adding heavy packages as optionalDependencies (keeps install free of scary deprecated warnings). Escape hatch: `ZEVAI_MINIMAL_POSTINSTALL=1` for light VPS/CI.

# v1.0.56 (2026-07-15)

## Changes
- **Cleaner global install**: drop heavy optional packages (`better-sqlite3`, `camoufox-js`, `cloakbrowser`, `node-pty`, `playwright`) from the published package so `npm i -g zevairouter` no longer prints scary `deprecated` warnings from transitive deps. Those packages still install on demand into `~/.zevai/runtime`. Nested npm installs use `--loglevel=error` for quieter postinstall.

# v1.0.55 (2026-07-15)

## Security
- **Terminal password reset is CLI-only**: removed browser reset API/UI. Forgot password → on the host: `zevai` → Settings → **Clear Terminal Password**, then set a new one in the Terminal page.

# v1.0.54 (2026-07-15)

## Fixes
- **Terminal first-run**: clearer setup vs unlock; **Forgot password** resets terminal password using dashboard password (so a leftover test hash is recoverable).

# v1.0.53 (2026-07-15)

## Changes
- **Terminal over tunnel**: remove localhost-only gate. Protection is dashboard auth + secondary terminal password (usable via tunnel when dashboard access is allowed).

# v1.0.52 (2026-07-15)

## Features
- **Terminal second password**: web shell requires a password different from the dashboard login (bcrypt, ~4h unlock cookie). First open forces setup; Lock button re-locks. Shell APIs return 423 until unlocked.

# v1.0.51 (2026-07-15)

## Fixes
- **CloakBrowser lazy install**: Grok bulk toast `Failed to install cloakbrowser … is not iterable` — `cloakbrowserRuntime` called `runNpmInstall` with the wrong argument shape (array first). Matches camoufox/playwright install path; package remains optional and installs into `~/.zevai/runtime` on first use.

# v1.0.50 (2026-07-15)

## Changes
- **Web terminal**: localhost-only shell in the dashboard (xterm + Python PTY bridge).
- **Playground scale**: static models first, capped live model fetches (no hang on 800+ connections); model search; temperature + system prompt.
- **Providers UX**: faster stats with 800+ connections, total/active banner, connection search on provider detail.
- **Sidebar**: Core / Advanced (collapsed by default) / System grouping.
- **Cleanup**: removed stale HTML redesign previews.

# v1.0.49 (2026-07-14)

## Changes
- **Remove Grok automation access key**: bulk register/login is open again for any logged-in dashboard user (same as before the key gate).

# v1.0.48 (2026-07-14)

## Security
- **Grok automation access key**: *(reverted in 1.0.49)* temporary shared-secret gate for Grok bulk APIs.

# v1.0.47 (2026-07-14)

## Fixes
- **Grok CLI bulk signup names**: use a random first/last name per account instead of hardcoding "Zevai Bulk".

# v1.0.46 (2026-07-14)

## Features
- **Grok CLI tempmail + CloakBrowser bulk**: Automation → Grok CLI can register accounts via `tempmail.zevai.web.id` (`@zevai.web.id`), solve Cloudflare Turnstile with **CloakBrowser** (headed + humanize), complete xAI email signup (OTP poll), then authorize Grok Build device code and save the connection. Input: `auto` / `auto:5` / `email:password`. New bulk engine option `cloakbrowser` (lazy install).

# v1.0.45 (2026-07-14)

## Features
- **Grok CLI bulk login**: Automation tab can bulk-login Grok CLI / Grok Build accounts via xAI device-code flow (Continue → Login with Google → GSuite credentials), with cookie dismiss, poll loop, and manual-assist fallback. Prefer **Camoufox** — headless Chromium is often rejected by Google.

# v1.0.44 (2026-07-14)

## Features
- **Quota Tracker expansion**: Cline, ClinePass, Kimchi, xAI, and Cursor now appear with usage/health probes (credits when the provider returns them; otherwise connected/session status).
- **Cline live models**: provider models API fetches the live Cline catalog when a connection is available.

# v1.0.43 (2026-07-14)

## Changes
- **Name cleanup**: MITM default key is now `sk_zevai`, UI/docs/i18n use ZevaiRouter only, and leftover upstream product-name strings are removed from the tree. Legacy tool configs and data dirs still migrate/read for compatibility.

# v1.0.42 (2026-07-14)

## Fixes
- **Quota Tracker**: include Grok CLI (and other usage-capable providers like qwen/iflow) in `USAGE_SUPPORTED_PROVIDERS` so connections show up beyond CodeBuddy/Kiro; improve Grok Build quota bar parsing.

# v1.0.41 (2026-07-14)

## Fixes
- **Endpoint Token Saver UI**: remove duplicate RTK/Caveman/Ponytail controls from Endpoint; settings live only on `/dashboard/token-saver` (link card remains for navigation).

# v1.0.40 (2026-07-14)

Wave-3 ports from upstream zevairouter: live catalogs + Headroom/PXPIPE token savers.

## Features
- **Live model catalogs**: ClinePass and GitHub Copilot fetch account-aware model lists (with Copilot token refresh) instead of lagging static lists.
- **Headroom**: optional external compress proxy (`headroom-ai`) for Claude/OpenAI/Responses/Kiro bodies; fail-open; dashboard Token Saver controls + start/stop API.
- **PXPIPE**: optional Claude-context image compression via `pxpipe-proxy` library install; settings + install/start/status APIs; wired into chat pipeline after Headroom.
- **Token Saver page**: new `/dashboard/token-saver` (sidebar) for RTK, Caveman, Ponytail, Headroom, and PXPIPE management.

# v1.0.39 (2026-07-14)

## Fixes
- **Grok CLI executor**: port missing `resolveSessionId` from session manager (build warned import was undefined; multi-turn session headers now resolve correctly).

# v1.0.38 (2026-07-14)

Wave-2 ports from upstream zevairouter: new OAuth/API providers + no-auth proxy rotation.

## Features
- **Kimchi (`kimchi`)**: browser-token OAuth gateway (`llm.kimchi.dev`) with specialized executor, live model catalog, and token paste in OAuth modal.
- **Grok CLI / Grok Build (`grok-cli` / `gcli`)**: device-code OAuth to `cli-chat-proxy.grok.com` (Responses API), dedicated executor, usage/billing quotas, and xAI token refresh.
- **ClinePass (`clinepass`)**: OAuth + API-key dual auth with ClinePass model catalog (shares Cline auth endpoints).
- **Perplexity Agent (`pa`)**: API-key multi-model Responses gateway (`api.perplexity.ai/v1/responses`).
- **No-auth proxy rotation**: round-robin / random strategies across active proxy pools (in-memory), with UI on the no-auth provider card.

# v1.0.37 (2026-07-14)

Wave-1 ports from upstream zevairouter + CodeBuddy CN polish.

## Features
- **MiMo Code Free (`mimo-free` / `mmf`)**: no-auth Xiaomi free endpoint with bootstrap JWT + anti-abuse marker (executor).
- **Venice AI** and **Featherless**: OpenAI-compatible API-key providers with seed model catalogs + passthrough.
- **Thinking level picker**: re-enabled on provider page; model chips copy as `alias/model(level)`; router strips suffix and applies `reasoning_effort`.

## Fixes
- **CodeBuddy CN stream**: strip empty `tool_calls: []` deltas that break AI SDK reasoning tracking.
- **CodeBuddy CN quota labels**: monthly refill vs one-shot gifts shown as **Monthly Credits** / **Bonus Credits** (all gift packs summed into one row).
- **CodeBuddy CN executor**: force stream + only send `reasoning_summary` when client requests reasoning.

# v1.0.36 (2026-07-14)

CodeBuddy CN phone bulk registration (5sim + Camoufox) + quota fix.

## Features
- **CodeBuddy CN phone bulk import**: dashboard modal + API to register accounts with 5sim virtual numbers (Hong Kong default), Camoufox headed browser, China proxy, SMS OTP, and auto API-key capture.
- **5sim settings**: store `fiveSimApiKey` in dashboard settings for automation.
- **Live preview + single-order safety**: one 5sim buy per account (no prefix re-order loop, no auto fallback to expensive `other` product); order only after the login form is ready; SMS poll timer starts after send-code click.

## Fixes
- **CodeBuddy CN quota "Usage not available"**: bulk-import saved `authType: "api-key"` but quota eligibility only accepted `"apikey"`. Normalized auth types and save path so CN connections show Monthly/Activity credits like other providers.
- **False SMS timeout after slow proxy nav**: 90s OTP window no longer starts at number purchase time (navigation could burn the whole window before "获取验证码").
- **Incomplete Keycloak session / API key capture**: longer OIDC handoff, functional console probes instead of hard-requiring only `KEYCLOAK_IDENTITY`, and needs_manual assist without re-ordering numbers.

# v1.0.35 (2026-07-06)

Ponytail token saver + proxy fix for Node 22.

## Features
- **Ponytail (lazy code style)**: new toggle in the Token Saver section alongside RTK and Caveman. Injects a "lazy senior dev" system prompt that enforces a YAGNI ladder — stdlib first, shortest diff, minimal code. Three levels: Lite (suggest lazier alternative), Full (enforce the ladder), Ultra (YAGNI extremist). Based on [ponytail](https://github.com/DietrichGebert/ponytail).

## Fixes
- **Proxy always failing on Node 22 (`fetch failed`)**: `globalThis.fetch` (Node 22's built-in undici ~6.x) is incompatible with `ProxyAgent` from the package's undici 8.7.0 — the dispatcher interface changed between versions, causing `invalid onRequestStart method` on every proxied request. All proxied requests now use `fetch` from the same undici package as `ProxyAgent`, so the dispatcher version always matches. Direct (non-proxied) requests still use the faster built-in fetch.

# v1.0.34 (2026-07-05)

Fix Antigravity automation broken since v1.0.30 stealth update — password field was being filled with the email address.

## Fixes
- **Password field filled with email**: after submitting the email, the polling loop would re-detect the transitioning email input before the password page fully loaded. With the v1.0.30 human-like typing delay (~12s per field), keystrokes typed into the disappearing email field landed in the password input instead. Fixed by checking the password field before the email field in the polling loop and adding a transition wait after the initial email submission.
- **User-Agent OS mismatch**: the stealth wrapper hardcoded a macOS User-Agent (`Macintosh; Intel Mac OS X`) regardless of the actual OS. On the Linux VPS, this mismatch between the UA string and `navigator.platform` could trigger Google's bot detection. Now auto-detects the OS and uses the matching platform string (Linux/macOS/Windows).

# v1.0.33 (2026-07-04)

Fix bulk delete button UX — buttons now follow standard styling and only appear when connections are selected.

## Fixes
- **Delete GSuite / Delete Gmail buttons** only appear after checking connection checkboxes (same behavior as Delete Selected). Previously they were always visible, breaking the selection-first workflow.
- **Button styling** now matches the default `Button` component (`variant="secondary"`) instead of custom orange/rose colors that looked out of place.

# v1.0.32 (2026-07-04)

Bulk delete by email domain + full quota aggregate + CodeBuddy API key support.

## Features
- **Bulk delete by email type**: new Delete GSuite and Delete Gmail buttons on the provider detail page. GSuite deletes all non-Gmail accounts (`@domain.com`), Gmail deletes all `@gmail.com` accounts. Useful for cleaning up large account pools by domain type.
- **CodeBuddy manual API key**: the CodeBuddy (non-CN) provider now supports adding connections via API key in addition to OAuth. Go to your [CodeBuddy profile](https://www.codebuddy.ai/profile) to grab your key.

## Fixes
- **Quota aggregate now reads ALL accounts**: the bulk usage view was only aggregating quotas from the ~20 connections visible on the current page. It now reads from the server-side quota cache (populated by the background sweep), so all 100+ accounts are counted in the totals.

# v1.0.31 (2026-07-04)

Server-side quota aggregate endpoint.

## Fixes
- **New `/api/usage/aggregate` endpoint**: reads all cached quota snapshots from SQLite instead of relying on client-side page data. Returns aggregated used/total across every active connection for the provider.

# v1.0.30 (2026-07-04)

Stealth Chromium for Antigravity bulk import.

## Fixes
- **Antigravity bulk import no longer gets blocked by Google bot detection.** The automation browser now launches with stealth flags (disable `AutomationControlled`, remove `webdriver` navigator property, spoof plugins/languages/webGL, realistic viewport). Previously Google detected the headless Chromium and blocked the login flow.

# v1.0.29 (2026-07-01)

Real fix: runtime optional packages no longer pruned (Camoufox stays installed).

## Fixes
- **Camoufox (and other lazy runtime packages) now persist across restarts.** Root cause: npm v11 prunes any package not listed in the runtime `package.json` on every `npm install`, so `--no-save` (from 1.0.28) actually made each install wipe the others — installing systray2/better-sqlite3 on startup deleted camoufox-js. Runtime installs now use `--save`, recording each optional package as a real dependency so npm keeps them. Verified live: installing systray2 after camoufox no longer removes camoufox.

# v1.0.28 (2026-07-01)

Fix Camoufox/optional runtime packages vanishing on restart.

## Fixes
- **Lazily-installed runtime packages no longer prune each other.** The runtime installer ran `npm install <pkg>` without `--no-save`, so installing one optional package (e.g. better-sqlite3 on startup) pruned the others (camoufox-js, playwright) — which is why Camoufox worked once then broke after a restart. All runtime installs are now `--no-save` (additive).
- **Runtime npm install uses the running Node.** On hosts where the default `npm`/`node` is an old version (e.g. Node 12 via `/usr/bin/node`), the installer re-exec'd under it and crashed. It now runs npm-cli.js with the same Node that's executing the app.

# v1.0.27 (2026-07-01)

Fix Camoufox engine "installed but cannot be required".

## Fixes
- **Camoufox bulk-import engine now loads reliably.** The lazily-installed `camoufox-js` package lives in `~/.zevai/runtime/node_modules`, but the standalone server's bundled require couldn't resolve it (MODULE_NOT_FOUND from a non-project cwd), even though the package and its browser binary were present. We now load it with a runtime-anchored `createRequire`, which uses real Node resolution and works from any working directory.

# v1.0.26 (2026-07-01)

Removed the Skills page.

## Changes
- Removed the "Skills" sidebar entry and page (the copy-a-URL agent-skills feature). Context Injection (soul.md/agent.md upload + inject) covers custom agent behavior now.

# v1.0.25 (2026-07-01)

Automation: typed accounts no longer lost when switching tabs.

## Fixes
- **Bulk automation keeps your typed accounts.** Switching between automation provider tabs (or anything that remounts the modal) used to wipe the accounts textarea, forcing you to paste them again. The accounts draft is now saved per-provider and restored automatically; it's cleared once the job actually starts.

# v1.0.24 (2026-07-01)

Fix Antigravity "Invalid JSON response" (all accounts erroring).

## Fixes
- **Antigravity chat now works again.** The anti-ban header scrub set `Accept-Encoding: gzip, deflate, br` manually. Under Node's fetch (undici), a manually-set Accept-Encoding disables automatic response decompression, so the upstream reply came back as raw gzip/brotli bytes and failed to parse ("Invalid JSON response from antigravity"). We no longer set Accept-Encoding — undici negotiates it and decompresses automatically. Verified with live chats across multiple accounts.
- Note: accounts whose Google tier requires a user-defined GCP project (`userDefinedCloudaicompanionProject`) still can't auto-onboard a project id — that's a Google account-type requirement, not a router bug. Free/auto-onboard accounts work.

# v1.0.23 (2026-07-01)

AutoClaw balance display + name cleanup.

## Fixes
- **AutoClaw balance now shows real consumption**: the wallet API only returns the current balance (not spend), so points appeared static. It now tracks a baseline (peak balance seen) and shows `used = baseline − remaining`, so the Usage bar reflects points actually consumed. Note: balance refreshes on the ~5-minute quota sweep, not instantly after each chat.
- **UI names**: replaced remaining visible product-name leftovers in the CLI Tools cards (MITM server, Antigravity, Droid, jcode, OpenCode) and MITM UI with ZevaiRouter. Functional identifiers (config profile keys, model ids like `custom:Zevai-*`, cert names, data-dir name) are intentionally left unchanged to avoid breaking existing user setups.

# v1.0.22 (2026-07-01)

Bulk upload for Context Injection.

## Features
- **Upload .md** button on the Context Inject page: multi-select many markdown files at once (e.g. a whole agent framework like SUPERAGENT). Each file becomes a context entry — the file name is the entry name, its content the injected text.
- Uploaded files are **disabled by default** so importing dozens of files doesn't inflate token cost on every request. Enable only the core files (SOUL, AGENTS, USER, registry) you actually want injected.

# v1.0.21 (2026-07-01)

Fix AutoClaw balance / "Unable to read balance".

## Fixes
- **AutoClaw balance & test connection** now authenticate the wallet endpoint with `authorization: Bearer <token>`. The wallet/asset API rejects the raw token (returns "user not logged in"), even though chat works with the raw `X-Authorization` token. Points now display and test connection passes for healthy accounts.

# v1.0.20 (2026-07-01)

New feature: Context Injection — inject your own system-prompt files into every request.

## Features
- **Context Injection** (Sidebar → Context Inject): create your own context files (soul.md, agent.md, rules…) that get prepended to the system prompt of every chat request routed through ZevaiRouter, across all providers and formats (OpenAI / Claude / Gemini / Antigravity).
  - Multi-file: add unlimited files, each with an enable toggle and injection order.
  - Global on/off switch; shows how much text is added per request.
  - Injection happens at one central point after format translation (mirrors the Caveman injector), so it works for every provider uniformly.

# v1.0.19 (2026-07-01)

AutoClaw connection fixes + name cleanup.

## Fixes
- **AutoClaw test connection**: the connection test now probes AutoClaw's wallet endpoint with its signed headers instead of the generic Bearer flow, fixing the misleading `[500] parse response failed` error on healthy accounts.
- **AutoClaw token handling**: access tokens are now stored and sent without a leading `Bearer ` prefix, so chat (`X-Authorization`) always receives the raw JWT.
- **UI names**: replaced remaining product-name leftovers in the OIDC settings (issuer/client ID placeholders), login, sidebar, MITM, skills, and endpoint UI with ZevaiRouter / the `zevai` CLI command.

# v1.0.18 (2026-07-01)

Automation page now shows real provider logos.

## Fixes
- The Automation page provider tabs and header used generic Material Symbols icons. They now render each provider's actual logo (Kiro, CodeBuddy, Qoder, Antigravity, AutoClaw), falling back to the icon only if the logo is missing.

# v1.0.17 (2026-07-01)

AutoClaw provider logo.

## Fixes
- Added the official AutoClaw logo so the provider no longer shows a plain text placeholder in provider lists and topology.

# v1.0.16 (2026-07-01)

New provider: AutoClaw (autoclaw.z.ai) — bulk auto-register + chat + points balance.

## Features
- **AutoClaw provider** (OpenAI-compatible GLM/DeepSeek proxy by Z.ai). Adds an Automation panel with bulk Google-OAuth auto-registration, chat routing, and per-account points balance.
  - Bulk register: paste `gmail:password` lines and the worker automates Google login, intercepts AutoClaw's tokens from the auth response (localStorage fallback), fetches the account's points, and saves the connection. Reuses the shared Google automation + auto-detect concurrency.
  - Chat: models `openrouter_glm-5.2` (GLM-5.2), `zai_glm-5-turbo` (GLM-5-Turbo), `zai_auto` (DeepSeek-V4-Pro), routed via `autoclaw/<model>`. Uses AutoClaw's signed headers (`X-Authorization`, `X-Request-Model`, MD5 `X-Auth-Sign`) and its own token refresh.
  - Usage: remaining points shown on the Usage/quota page.

# v1.0.15 (2026-07-01)

Bulk import now auto-tunes worker count to the host — no more CPU pegged at 100% on small VPS.

## Improvements
- **Bulk import "Auto-detect" concurrency is now ON by default**: worker count is derived from the server's CPU/RAM (clamped to 1–8) instead of always starting at 4. On a small VPS (e.g. 2 vCPU / 2 GB) this settles at 1–2 workers, avoiding the CPU spike to 100% that happened when 4 headless browsers launched at once. You can still uncheck it and set the worker count manually.

# v1.0.14 (2026-07-01)

Name cleanup — replaced leftover product-name references in user-facing surfaces with ZevaiRouter.

## Fixes
- Usage → Provider Topology: the center node now reads **ZevaiRouter** instead of "ZevaiRouter".
- Changelog content cleaned of stale upstream product names and old Docker image names.

# v1.0.13 (2026-07-01)

Smart routing by remaining quota — requests now prefer accounts with the most quota left.

## Features
- **Smart routing by remaining quota**: the executor can route each request to the account with the highest remaining quota, spreading load and reducing the chance of hitting per-account limits.
- Server-side quota cache: remaining-quota reads are cached server-side to avoid repeated upstream lookups on every request, cutting latency and upstream call volume.

# v1.0.12 (2026-07-01)

Antigravity anti-ban hardening, quota reading fix, and proxy rotation.

## Improvements
- **Anti-ban hardening** for the Antigravity provider to reduce the risk of account flags.
- **Quota reading fix**: remaining-quota values now parse correctly from upstream responses.
- **Proxy rotation**: outbound requests can rotate through configured proxies to distribute traffic.
