sync: integrate upstream main through c0106e50 - #209
Conversation
…TF-8 child streams First real-world run of the NousResearch#82328/NousResearch#82366 hand-off (2026-08-09, ryanc) surfaced two defects: 1. The console window never closes after the update finishes -- and closing it manually KILLS the freshly relaunched GUI. Root cause: Start-DesktopRelaunch spawned Hermes.exe as a child of the console PowerShell. Electron/Chromium calls AttachConsole(ATTACH_PARENT_ PROCESS) at boot, so the new Desktop latched onto the hand-off's console: the console can't close while an attached process lives, and closing it takes the attached GUI down with it. Fix: create the process via WMI (Win32_Process.Create) -- parent becomes WmiPrvSE, no console to inherit or attach, same detachment explorer.exe gives a normal launch. Start-Process fallback retained (tethered Desktop beats no Desktop). 2. Both the console and the progress box render hermes update's UTF-8 glyphs (checkmarks, arrows) as mojibake. PS 5.1 defaults redirected child streams to the OEM codepage. Fix: StandardOutput/ErrorEncoding = UTF8 on the child, PYTHONIOENCODING/PYTHONUTF8 so Python emits UTF-8, and [Console]::OutputEncoding = UTF8 for our own echo. Verified live on the incident machine: WMI-created process parents to WmiPrvSE.exe (not the shell); UTF-8 glyph round-trip through the exact ProcessStartInfo shape reads back byte-correct (15/15 chars). PS 5.1 parse clean, check-windows-footguns clean.
…ofiles _PROVIDER_PREFIXES was a hand-maintained frozenset, so providers that ship as plugins (bundled like fireworks, or user plugins under $HERMES_HOME/plugins/model-providers/) were never recognised as provider: prefixes in model strings, and metadata/context-window lookups received the unstripped string. Mirror the _URL_TO_PROVIDER auto-extend that already sits below it: add each registered profile's name and aliases after discovery. The _OLLAMA_TAG_PATTERN guard keeps model:tag strings intact. Fixes NousResearch#66106
…e relaunched Desktop Two focus polish items from the first fully-working hand-off run (ryanc, 2026-08-09): 1. The progress window came up backgrounded: the script is spawned via `cmd start /min`, and Form.Show() + TopMost keeps it above other windows without ACTIVATING it. Claim activation explicitly (Form.Activate + SetForegroundWindow) right after Show. 2. The relaunched Desktop came up behind whatever the user had focused: a WMI-spawned process starts unfocused and cannot take foreground by itself. Since the hand-off owns foreground while its progress window is up, delegate it: AllowSetForegroundWindow(new pid), poll up to 20s for Electron's MainWindowHandle, then ShowWindow(SW_RESTORE) + SetForegroundWindow. Best-effort at every step -- a focus failure never affects the update result. Sequence on success: progress window foreground during the update -> window closes -> freshly relaunched Hermes.exe takes foreground. Verified live on the incident machine: Add-Type shim compiles under PS 5.1; WMI spawn + AllowSetForegroundWindow + MainWindowHandle poll + ShowWindow all execute against a real spawned window. (In the bg test shell SetForegroundWindow returns False by OS design -- only the current foreground owner may delegate; the real flow's TopMost progress window IS that owner.) PS parse clean, check-windows-footguns clean.
…usResearch#82319) Extend the packaged-app HUD geometry test from horizontal-only to full containment: both axes for the dock and the input, plus an explicit assertion that no percentage translate survives on the composer dock. The vertical clipping reported on Windows (NousResearch#82203) and macOS (NousResearch#82214) is the same escape class on the other axis, and the computed-translate probe makes a future optimizer regression fail with a diagnosis instead of a bare coordinate mismatch.
…arch#82360) * fix(desktop): keep the HUD on the session it was opened for The HUD is a full app renderer, so the main window's cold-start 'restore last session' logic ran inside it: opening HUD on a blank new chat (#/) navigated it to the remembered session instead of the new one, because a blank draft has no stored id and the HUD boots at the default route. Guard the restore/remember effect with isHudWindow() — the HUD's destination is always chosen explicitly at open time. Also stops the HUD from clobbering the main window's remembered navigation while it is up. * fix(desktop): use type-only import for the windows-store mock in HUD restore test consistent-type-imports forbids inline import() type annotations; use the established import type * as pattern (same as session-row.test.tsx).
…usResearch#82325) * fix(desktop): open HUD mode on the focused conversation's profile The HUD is a full app renderer that adopted the PRIMARY backend's profile at boot, so toggling HUD mode from a conversation on any other profile resolved the session id against the wrong backend — the lookup missed and the HUD fell back to the default profile's last session (NousResearch#82285). - openHud() resolves the target's owning profile (session's stamped owner, else the active gateway profile) and passes it through hermes:hud:open. - hudUrl() carries the profile in the query string next to win=hud; the HUD renderer's gateway boot honors it as an override for both getConnection() and profile adoption, so the window dials and adopts the right backend from first paint. - Retargeting a live HUD onto a session from a DIFFERENT profile respawns the window against that profile's backend (a renderer adopts its backend exactly once at boot; an in-place goto would repeat the wrong-backend lookup). No profile in the URL means no override — ordinary windows and single-profile users boot exactly as before. * refactor(desktop): extract the HUD renderer URL so its contract is tested hudUrl() built the query string inline in main.ts, where the part that actually breaks — `?win=hud&profile=` must sit BEFORE the '#' or HashRouter eats it as the route — had no coverage. Move it next to buildSessionWindowUrl's split (pure piece out of the monolith, unit tested) and pin the contract: flag order, profile encoding, trailing slash on the dev server, empty profile omitted, packaged file URL. Co-authored-by: rainbowgore <rainbowgore@users.noreply.github.com> * refactor(desktop): resolve the HUD's target profile through the existing ladder openHud() had its own copy of "stamped owner, else active gateway, else default" — the same ladder rememberedSessionProfile() already owns for the remembered-navigation key, down to sessionMatchesStoredId and the default fallback. One resolver per policy, so the two can't drift. --------- Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com> Co-authored-by: rainbowgore <rainbowgore@users.noreply.github.com>
…no focus (NousResearch#82403) The chip was pointer-events: none until [data-slot='composer-rich-input'] had :focus, which made the only visible way out of HUD mode conditional on the thing most likely to be broken when someone wants out. When focus never lands (NousResearch#81893 on macOS) you can neither type nor click your way out: the HUD is a transparent always-on-top rectangle over the desktop with no in-app dismiss. It is now always clickable and dim (0.45) at rest, brightening on hover, focus-visible, and composer focus. That keeps the original intent — not a loud chip over the app behind — without gating the escape hatch on the failure mode it exists for. Salvaged from NousResearch#82317 by @Ne0teric. The centering half of that PR is dropped: NousResearch#82233 already fixed the dock offset, and its 'translate: none' is the exact literal Lightning CSS folds into 'transform', which is the bug NousResearch#82233 fixed. Co-authored-by: Ne0teric <Ne0teric@users.noreply.github.com>
A draft got no dot at all, so the one tab that has never done anything looked identical to a settled session. Give it the faintest mark the app has — a hollow outline, weakest claim in the dot's priority order, so the first thing that actually happens speaks over it. The row's own message_count is the tiebreaker for what counts as a draft: a session RESUMING also holds an empty message list for a moment, and calling that a draft flashes the wrong mark on a conversation with years of history in it.
Every unsent tab was called "New session", so a row of them said nothing about which was which. Name each one from its composer, using the same first-line, word-boundary rule the backend's derive_title applies a moment after the draft is finally sent — so the name the tab already shows is the name it keeps. The title moves with the composer, which is far faster than a pane contribution should be re-registered. Panes can now render a tab label instead of declaring one, so the label subscribes to its own key and a rename repaints one string rather than the panes area.
The turn prologue titles every session, and it is shared by every agent — including the ones no person is reading. A cron job already names its own session after the job in its finally block, so the titler spent a side-LLM call per fire to write the delivery scaffolding over it for the length of the run. A delegated child's session is hidden from every picker, so a batch at max_concurrent_children paid N title calls for N names nobody opens. Both are the same class of run that already sets skip_memory to stay off the auxiliary path, so keep the titler off it too.
Titling is two-stage — a slice of the user's own words lands inline, the model's version replaces it a second later — and the platform rename lanes fired on both. That is two rate-limited calls to reach one name, and Discord allows two channel renames per ten minutes, so the throwaway could be the one that survived. The callback now carries which stage it is, and the lanes take the model's. The relay lane also asked where the reply landed at title time, which is before the model has answered: it polled the send-result cache for ten seconds and read the timeout as "never auto-threaded", so any turn with tool calls in it silently kept its raw thread name. Wait on the send itself instead — the adapter already owns that cache, so it can say when a reply arrives and, just as usefully, that one arrived carrying nothing.
The fast-model picker reads /v1/models to find the small model a provider currently serves, and it asked anonymously. Most of those endpoints need a key, so the fetch 401'd and the empty result read as "this provider has no small model" — the picker fell back to its curated list and never noticed. Worse, a failed fetch cached its empty result forever, so one bad moment during startup disabled live model discovery for the life of the process, and the processes that read this run for weeks. Give the failure an expiry and pass the provider's credentials. The bare family rungs (-mini, -flash, haiku) also picked whichever id sorted first, which is the oldest generation a provider still serves: gpt-3.5-mini over gpt-5.4-mini, claude-3-haiku over claude-haiku-4.5. Compare the digit runs as numbers so the rung meant to keep us current does.
An opener is not always titleable — an image with no caption, a compaction handoff, a bare slash command — and those sessions stayed unnamed for life, because the guard that stops re-titling a named session also stopped the nameless one from ever asking again. Let a later turn name a session that still has no title. The derived title also ran the collision dedupe inline on the turn. It is a slice of the user's own words, so it collides constantly — people open sessions with "hi" — and resolving "hi #47" is a widening scan on the critical path for a name the model replaces a second later. Decline it there and let the background stage, which can afford the scan, pick it up.
…S model Two lookalike gaps found auditing the titler. _MACHINE_PREFIXES missed the compressor's legacy summary opener and the "[System note:" injections, so a compacted or resumed session could be named after the note that carried it. Take the summary prefix from the compressor that emits it rather than keeping a fourth local copy. The fast-model exclude list covered embedders but not the other non-chat siblings a provider names after its chat model — "gpt-4o-mini-tts" satisfies the "-mini" rung and cannot answer a prompt.
Switching models before sending the first real message titled the session "[System: The active model for this chat has…" instead of the user's actual question. `_append_model_switch_marker` persists its notice with `role="user"` because strict OpenAI-compatible providers reject a system message that is not first (NousResearch#48338). Titling had no way to tell that apart from a genuine opening turn, which caused two distinct failures: 1. `_MACHINE_PREFIXES` did not cover the marker. Its `[System: ` prefix matches none of `[CONTEXT COMPACTION`, `[Runtime note:`, or `[SYSTEM]` (different case, no closing bracket), so `is_titleable_user_message()` returned True and the marker was formatted into the title. 2. `maybe_auto_title()` counted the marker as a user message. With the marker present, the first real question arrived at `user_msg_count == 2` and the `> 1` guard returned early, so the session was never titled at all and its `title` column stayed NULL. Fixing only (1) would therefore have traded a wrong title for a permanently missing one. Add the marker prefix to `_MACHINE_PREFIXES` (kept in sync with `tui_gateway.server._MODEL_SWITCH_MARKER_PREFIX`) and count only titleable user messages when detecting the opening turn. The guard stays narrow: ordinary user text that happens to start with "[System:" still titles normally. Adds 6 regression tests, verified to fail without the fix.
Folds the model-switch fix in with the untitled retry. They answer different halves and each is wrong alone: counting alone left a session that merely opened with machinery nameless forever, because nothing reconsidered it, and the stored title alone would never title at all on a store too old to report one. Skip only when both agree — past the opening turn, and already named. Counting a turn now judges a multimodal one on its text, so "here's a screenshot, fix the login" counts as the question it is rather than reading as machinery and undercounting the conversation. Co-authored-by: yy28 <yy28@vip.sina.com>
fix(desktop,title): name and mark an unsent session, and the titler behind it
…arch#82226) `read_window_below` enumerates through get-windows, which on Linux reads `_NET_CLIENT_LIST_STACKING` via xprop. That is an X11 protocol, and Wayland deliberately refuses to tell one application about another's windows. Under XWayland it is worse than nothing: it finds the few legacy X11 clients and silently misses every native Wayland window, which on a Hyprland desktop is most of them — so the HUD floats over an app it cannot name. Hyprland answers the question directly. `j/clients` on its command socket returns every window with class, title, position, size, pid and focus history. Ask it first when HYPRLAND_INSTANCE_SIGNATURE is set, fall back to get-windows everywhere else, and keep the picking logic shared and unchanged. Three things the provider has to get right, all covered by tests: order comes from focusHistoryID rather than the list; windows on other workspaces are dropped, since they share coordinates with the visible ones and would win the overlap test; and our own window is left out, because focus history is not stacking order — the HUD floats on top while the user works underneath it, so slicing after ourselves would skip past the very app we are trying to report. One request per tool call, opened and closed immediately: Hyprland evaluates this socket synchronously and freezes until a five-second timeout on a connection left hanging.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…boundary (NousResearch#81867) Webhook/cron skill invocations concatenate a large static scaffold (activation note + expanded skill body) with a small volatile tail (ticket payload, timestamps) into one user string, and the Anthropic cache planner marked that whole string as a single atomic block — so a few changed tail bytes forced a full cache rewrite on every invocation. Instead of re-parsing scaffold marker strings out of the message at request time (fragile when a payload or skill body quotes the marker), the builders now register the exact stable-prefix bytes in a small process-local LRU registry at construction time. The cache planner splits a registered user string into [marked stable prefix, unmarked volatile tail] request-locally; canonical session history stays a plain string, and the failover stripper flattens the split back byte-exactly via an O(1) registry lookup. Unregistered messages keep the existing whole-message policy. Covers the single-skill builder (webhook + slash command + TUI) and the cron job prompt assembler (multi-skill, bundles, skipped-skill notice), with registration guarded against injection-scanner sanitization. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…emory growth Follow-up review of the builder-declared cache boundary (NousResearch#81867) found three ways the split could silently stop paying off, or keep paying more than it should, on a long-lived gateway process. Flattening no longer consults the registry. `strip_anthropic_cache_control` matched the decorated split by looking the first block up in the prefix registry, so a mid-turn failover that re-decorates a request built many messages earlier (NousResearch#72626) would fail to flatten once _MAX_ENTRIES newer scaffolds had been registered in between, and would hand the next provider the two-part shape instead of the canonical string. The split is now matched by its shape: a marker on the *first* part of a user message is something no other decoration produces (list content otherwise gets its marker on the last part, and the two-part [static, volatile] split is role-gated to system), so the ""-join stays provably byte-exact without any process state. This drops `is_registered_stable_prefix` and one lock acquisition per stripped message. Lookups now refresh LRU position. A scaffold fired every minute by cron could be evicted by a burst of one-off skill invocations while still being the hottest prefix in the process, silently reverting it to whole-message caching. Registration now also evicts by total retained bytes (4 MiB). Entries hold whole expanded skill bodies, so a 32-entry cap alone does not bound memory. The newest entry is always kept, so a single oversized scaffold still gets a boundary instead of disabling the split. Tests: eviction-then-failover round-trip, LRU refresh on hit, byte-cap eviction, and oversized-single-entry survival. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he registry Follow-ups from review of NousResearch#82049: - extract append_user_instruction() into agent/skill_commands so the stable-prefix construction cannot drift between the skill and cron builders (the registered prefix must stay a byte-prefix of the built message); cron no longer imports the private _SINGLE_SKILL_INSTRUCTION - add the startswith guard to the skill builder registration site, matching the stronger cron guard - rename _MAX_BYTES to _MAX_CHARS (sum(map(len, ...)) counts characters, not bytes) and correct the comment - collapse find_stable_prefix's two-lock dance into a single critical section (scan is <=32 short-circuiting startswith calls, measured 2-4us; drops the snapshot copy and the TOCTOU re-check) - document the split-shape lifetime (marked-endpoint window) in the module docstring - add a contract test for the helper's byte-prefix invariant (mutation-checked)
A background memory/skill review (agent/background_review.py) forks a second, complete AIAgent in a daemon thread that deliberately shares the live agent's own session_id for prompt-cache warmth. Nothing previously stopped a user's next live turn from starting while that fork was still mid-conversation, letting both stream against the same session_id and credentials concurrently. That produced two observable failures: - Doubled prompt-token accounting on the live turn's own calls (the two concurrent request/response streams under one session_id confuse the token-usage bookkeeping), triggering premature context compression. - A lockup that a normal interrupt could not clear: the review fork is a fully independent AIAgent with its own _interrupt_requested flag, and was never added to the parent's _active_children list -- the only list AIAgent.interrupt() actually walks for cross-agent cancellation -- so a live-turn Ctrl+C had no propagation path to it at all. Fix, three files: 1. agent/agent_init.py -- add _background_review_agent / _background_review_lock tracking state to every AIAgent, mirroring the existing _active_children pattern. 2. agent/background_review.py -- the review fork now registers itself on the parent's _active_children right after construction (reusing the same list/lock interrupt() already fans out to for real subagent delegation), and unregisters on every exit path (success, the tool-whitelist finally, and the outer exception safety-net). All registration is defensive (getattr/try-except) so an AIAgent built without going through agent_init.py's setup degrades to "no cross-turn cancellation" instead of aborting the whole review. 3. agent/conversation_loop.py -- at the very start of every run_conversation() turn, if a prior background review is still in-flight, it is now proactively cancelled via interrupt() before the live turn proceeds -- fire-and-forget, non-blocking, adds no latency. Adds 3 regression tests to tests/run_agent/test_background_review.py, confirmed to fail against the pre-fix code via a scripted revert. Verified: ruff clean on all touched files; 66/66 background-review and interrupt-propagation tests pass; 256/256 across turn_finalizer + run_agent regression suites; no fork-only symbols in the diff.
…r test Simplify registration/unregistration to match delegate_tool.py's hasattr+getattr pattern instead of over-defensive try/except Exception blocks. Delete inspect.getsource() change-detector test (breaks on rename, proves nothing the behavioral test doesn't cover). Net: -73 lines, +35 lines = -38 lines.
…compaction aed114a taught _is_synthetic_compression_user_turn to recognize the max-iteration nudge as ephemeral runtime scaffolding rather than a human turn, since its role="user" metadata flag doesn't survive SessionDB projection and a crash/interrupt mid-turn can persist it durably — becoming the compaction anchor / auto-focus topic in place of the real task. conversation_loop.py's retry loop appends several more role="user" rows with the exact same "ephemeral, metadata-tag-only" shape, none of them recognized by the classifier: - The three _get_continuation_prompt variants (length-continuation nudge, tagged _length_continuation_nudge) — two fixed strings plus a third that interpolates the dropped-tool-call list. - _CODEX_INCOMPLETE_NUDGE (codex/responses reasoning-only retry). - The codex ack-continuation nudge (acknowledgment-only reply re-prompt). - The dropped-tool-call nudge (tagged _dropped_toolcall_nudge) — persisted across up to 3 consecutive retries before the finalization pop-loop strips it; an interrupt/crash before that pop can persist it same as the max-iteration case. Promote the previously-inline nudge strings to named module-level constants in conversation_loop.py (single source of truth for both construction and recognition), then extend the classifier to recognize all of them — exact match for the five fixed-content nudges, a stable-prefix check for the dropped-tool-call continuation variant (its tool list is interpolated so it can't be exact-matched, same treatment TODO_INJECTION_HEADER already gets). Imported lazily inside the classifier to avoid a module-load-order cycle — conversation_loop.py already imports FROM context_compressor.py at call time for the same reason.
…dge sibling
Fix: _LENGTH_CONTINUATION_DROPPED_TOOLS_PREFIX ended with '(' but
_get_continuation_prompt still had f'({tool_list})', producing
'((write_file)' instead of '(write_file)'. Removed the '(' from
the prefix constant — the parenthesis belongs in the interpolation.
Widened: promoted the empty-response nudge (line 6993,
'You just executed tool calls but returned an empty response...')
to _EMPTY_TOOL_RESPONSE_NUDGE constant and added it to the
classifier's recognition set. Same bug class — its
_empty_recovery_synthetic metadata flag doesn't survive SessionDB
projection either.
Test: added parametrize case for the empty-response nudge (7→8 cases).
E2E: verified byte-for-byte string equivalence for all nudge constants.
…al login windows (NousResearch#81290 follow-up) @spfcraze's triage review noted the PR description claimed "every BrowserWindow" but the OAuth and portal sign-in windows were not wired: a crashed sign-in renderer leaves the window's promise path never settling, with no trace in desktop.log. Wire both with the same log-only lifecycle diagnostics as the overlay and quick windows — `kind: 'oauth'` and `kind: 'portal'` respectively. Neither window gets crash-reload treatment (a sign-in window that reloads itself mid-auth would be surprising); the lifecycle helper's log-only callback is the exact contract needed here. window-renderer-lifecycle.test.ts: 17/17 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re window-reveal (NousResearch#81290) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconcile the salvaged NousResearch#81533 lifecycle helper with the renderer-log console pipeline that landed in NousResearch#83535 (the two PRs raced): - window-renderer-lifecycle.ts no longer handles console-message — renderer-log.ts is the single owner (per-window labels, boundary reports). One owner means no double-logged errors on windows wearing both, and OAuth/portal windows (lifecycle-wired for process events) cannot spill third-party page console output into desktop.log. - wake indicator window gets attachRendererConsoleCapture, keeping the console coverage it previously got from the helper. - HUD window (added after the PR branched) gets log-only lifecycle coverage — it was the one renderer window the PR couldn't have known about. - Tests updated: lifecycle helper asserts it attaches NO console-message listener; parser tests live in renderer-log.test.ts.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The custom-endpoint REST handlers ran bare load_config/save_config, so every add/activate/delete landed in the process-level default profile regardless of which profile the desktop settings UI was targeting. A provider added under a non-default profile silently went to default: visible only in default-bound sessions, absent everywhere else, and un-addable to another profile without hand-editing its config.yaml. Scope all four handlers (list/upsert/activate/delete) to the requested profile via _config_profile_scope, matching /api/config, and spread the active profile into the four hermes.ts wrappers alongside their existing validateCustomEndpoint sibling.
get_env_value/load_config read through the shared os.environ mirror that save_env_value writes, so a reader-based assertion cannot prove which profile's store actually received the write. Read the two profiles' config.yaml and .env directly instead, and cover the credential path.
…vider-profile fix(desktop): scope custom provider settings to the active profile
…rst run The first-run provider picker showed Fireworks AI alongside Nous Portal before the user opened the 'Other providers' disclosure. Only Nous Portal should be visible up front; Fireworks now lives inside the expanded list but keeps its #1 position there (Nous -> Fireworks ordering preserved).
The publisher read the PR number from the CI run's pull_requests payload. GitHub keeps that payload empty for fork runs, so the job printed 'No pull request is associated' and stopped on every fork PR. Resolve the PR from the run's head owner, branch, and SHA instead. The SHA match skips runs that a newer push superseded. A fork PR also has no CI review comment, because the live poller skips forks. The publisher now logs this and exits clean instead of raising; the evidence stays in the workflow artifact.
The Kimi team noticed that traffic from Hermes Coding Plan users
identifies itself as Claude (User-Agent: claude-code/0.1.0) rather
than the actual client. They asked us to update the UA so they can
properly attribute traffic and understand how their services are
accessed — especially important as they open up to more third-party
agents.
Three code paths were sending wrong/attribution-less headers to Kimi:
1. run_agent.py — _apply_client_headers_for_base_url sent
{"User-Agent": "claude-code/0.1.0"} for api.kimi.com. Now sends
the same _AI_GATEWAY_HEADERS set used for Vercel AI Gateway:
HTTP-Referer + X-Title + HermesAgent/{version} User-Agent.
2. agent/anthropic_adapter.py — the Anthropic Messages path for
api.kimi.com/coding sent 'claude-code/0.1.0'. Now sends the same
three-header attribution set.
3. plugins/model-providers/kimi-coding/__init__.py — both kimi and
kimi_cn profiles sent a static 'hermes-agent/1.0' with no
HTTP-Referer or X-Title. Now sends the full three-header set with
a dynamic version, matching the pattern used by the gmi, fireworks,
xai, and ai-gateway provider profiles.
The attribution header set (HTTP-Referer + X-Title + User-Agent) is
the canonical Hermes pattern used for OpenRouter, Vercel AI Gateway,
Fireworks, and other providers that read these headers for traffic
attribution.
|
Important Review skippedToo many files! This PR contains 227 files, which is 127 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (227)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
૮ >ﻌ< ა ci reviewrunning on 86e3354 — Merge upstream/main into Stanislav's live fork waiting for jobs to start… |
b118081 to
e3a0f52
Compare
|
Remediation update for exact candidate 86e3354:
Fresh exact-SHA review and hosted CI are running. The ci-reviewed gate remains intentionally closed pending PASS. |
e3a0f52 to
86e3354
Compare
|
Independent review result for exact candidate 86e3354 (tree df1c4b2009cd4668673288a21b0fb25b1cdda107): verdict=PASS. The reviewer verified:
Hosted code, security, compatibility, platform, packaging, and shadow checks are green; the final desktop UI check is still completing. Based on this exact-SHA independent PASS, the ci-reviewed label is approved. Live deployment remains a separate gate. |
Summary
Verification
Deployment
No service activation in this PR. Live gateway remains on the pre-update process until this candidate passes review/gates.