Skip to content

sync: integrate upstream main through c0106e50 - #209

Merged
batumilove merged 990 commits into
batumi/livefrom
sync/upstream-update-20260811T081410Z
Aug 11, 2026
Merged

sync: integrate upstream main through c0106e50#209
batumilove merged 990 commits into
batumi/livefrom
sync/upstream-update-20260811T081410Z

Conversation

@batumilove

Copy link
Copy Markdown
Owner

Summary

  • merge upstream/main at c0106e5 into batumi/live
  • preserve classified Batumi fork patches and Honcho contention debounce
  • advance .github/upstream-base to c0106e5
  • remove upstream-compromised Blender MCP integration

Verification

  • fork-delta ownership check: pass (149 classified paths, 0 unexplained)
  • merge resolver focused suites: 819 passed, 22 skipped
  • Desktop typecheck: pass
  • Desktop focused Vitest: 94 passed
  • post-review focused suite: 226 passed, 1 skipped
  • WAL ambiguous-EIO regressions: 2 passed
  • Batumi contract run: all completed files green except tests/test_hermes_state.py hit the 300s parallel timeout under jbd2 I/O contention; its only observed assertion failure was corrected and the two affected regressions pass. Session hygiene transiently failed under the same load and passed its built-in retry (17/17).

Deployment

No service activation in this PR. Live gateway remains on the pre-update process until this candidate passes review/gates.

teknium1 and others added 30 commits August 9, 2026 01:43
…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.
Enough1122 and others added 16 commits August 10, 2026 19:00
…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.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Too 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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e942093-b400-4e01-92b5-017404fea824

📥 Commits

Reviewing files that changed from the base of the PR and between 74e7884 and 86e3354.

⛔ Files ignored due to path filters (6)
  • package-lock.json is excluded by !**/package-lock.json
  • plugins/kanban/dashboard/dist/index.js is excluded by !**/dist/**
  • plugins/kanban/dashboard/dist/style.css is excluded by !**/dist/**
  • plugins/platforms/photon/sidecar/package-lock.json is excluded by !**/package-lock.json
  • uv.lock is excluded by !**/*.lock
  • website/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (227)
  • .github/batumi-patches.yaml
  • .github/upstream-base
  • .github/workflows/ci-review-comment.yml
  • .github/workflows/publish-e2e-evidence.yml
  • .github/workflows/tests.yml
  • AGENTS.md
  • CONTRIBUTING.md
  • acp_adapter/tools.py
  • agent/anthropic_adapter.py
  • agent/auxiliary_client.py
  • agent/browser_provider.py
  • agent/display.py
  • agent/prompt_builder.py
  • agent/transports/hermes_tools_mcp_server.py
  • apps/desktop/electron/backend-child.ts
  • apps/desktop/electron/hud-snap-shortcut.test.ts
  • apps/desktop/electron/hud-snap-shortcut.ts
  • apps/desktop/electron/hud-snap.test.ts
  • apps/desktop/electron/hud-snap.ts
  • apps/desktop/electron/main.ts
  • apps/desktop/electron/preload.ts
  • apps/desktop/electron/remote-lifecycle.test.ts
  • apps/desktop/electron/remote-lifecycle.ts
  • apps/desktop/electron/renderer-log.test.ts
  • apps/desktop/electron/renderer-log.ts
  • apps/desktop/electron/wake-indicator-window.ts
  • apps/desktop/electron/wake-indicator.test.ts
  • apps/desktop/electron/window-renderer-lifecycle.test.ts
  • apps/desktop/electron/window-renderer-lifecycle.ts
  • apps/desktop/electron/windows-child-options.test.ts
  • apps/desktop/eslint.config.mjs
  • apps/desktop/scripts/perf/lib/launch.mjs
  • apps/desktop/scripts/perf/scenarios/multitab.mjs
  • apps/desktop/src/app/chat/index.tsx
  • apps/desktop/src/app/chat/route-tile.tsx
  • apps/desktop/src/app/chat/transcript-window.test.ts
  • apps/desktop/src/app/chat/transcript-window.ts
  • apps/desktop/src/app/contrib/panes.tsx
  • apps/desktop/src/app/contrib/surfaces.tsx
  • apps/desktop/src/app/contrib/wiring.tsx
  • apps/desktop/src/app/shell/statusbar-controls.tsx
  • apps/desktop/src/app/shell/titlebar.test.ts
  • apps/desktop/src/app/shell/titlebar.ts
  • apps/desktop/src/components/assistant-ui/thread/list.tsx
  • apps/desktop/src/components/error-boundary.tsx
  • apps/desktop/src/components/onboarding/index.test.tsx
  • apps/desktop/src/components/onboarding/index.tsx
  • apps/desktop/src/components/onboarding/providers.tsx
  • apps/desktop/src/components/pane-shell/tree/renderer/floating-panes.tsx
  • apps/desktop/src/components/pane-shell/tree/renderer/narrow-overlays.tsx
  • apps/desktop/src/components/pane-shell/tree/renderer/tree-group.tsx
  • apps/desktop/src/contrib/react/boundary.tsx
  • apps/desktop/src/contrib/react/slot.test.tsx
  • apps/desktop/src/contrib/react/slot.tsx
  • apps/desktop/src/global.d.ts
  • apps/desktop/src/hermes.ts
  • apps/desktop/src/i18n/en.ts
  • apps/desktop/src/lib/inflight-turn-journal.test.ts
  • apps/desktop/src/lib/inflight-turn-journal.ts
  • apps/desktop/src/lib/keybinds/actions.ts
  • apps/desktop/src/main.tsx
  • apps/desktop/src/store/session-states-eviction.test.ts
  • apps/desktop/src/store/session-states.ts
  • apps/desktop/src/store/session.ts
  • cli-config.yaml.example
  • cli.py
  • contributors/emails/XiaoZAZA@users.noreply.github.com
  • contributors/emails/a_espinosa@live.com
  • contributors/emails/ilovethevikings@yahoo.com
  • contributors/emails/laithweinberger@gmail.com
  • contributors/emails/michael@smfworks.com
  • contributors/emails/nikita.barkov@jetbrains.com
  • contributors/emails/phull@phullcutz.de
  • contributors/emails/unashamed366@gmail.com
  • contributors/emails/zqw3719222@163.com
  • gateway/config.py
  • gateway/kanban_watchers.py
  • gateway/platforms/api_server.py
  • gateway/platforms/base.py
  • gateway/platforms/webhook.py
  • gateway/profile_routing.py
  • gateway/relay/adapter.py
  • gateway/relay/ws_transport.py
  • gateway/run.py
  • gateway/session.py
  • gateway/session_context.py
  • gateway/stream_consumer.py
  • hermes_cli/auth.py
  • hermes_cli/banner.py
  • hermes_cli/cli_agent_setup_mixin.py
  • hermes_cli/cli_commands_mixin.py
  • hermes_cli/commands.py
  • hermes_cli/config_defaults.py
  • hermes_cli/curses_ui.py
  • hermes_cli/dashboard_procs.py
  • hermes_cli/gateway.py
  • hermes_cli/goals.py
  • hermes_cli/honcho_monitor.py
  • hermes_cli/kanban.py
  • hermes_cli/kanban_db.py
  • hermes_cli/kanban_diagnostics.py
  • hermes_cli/kanban_swarm.py
  • hermes_cli/main.py
  • hermes_cli/plugins.py
  • hermes_cli/profiles.py
  • hermes_cli/resource_limits.py
  • hermes_cli/tools_config.py
  • hermes_cli/web_server.py
  • hermes_state.py
  • hermes_state_schema.py
  • model_tools.py
  • optional-mcps/blender/manifest.yaml
  • optional-skills/creative/blender-mcp/SKILL.md
  • optional-skills/creative/blender-mcp/references/bpy-api.md
  • optional-skills/creative/blender-mcp/references/pitfalls.md
  • optional-skills/creative/blender-mcp/references/recipes.md
  • optional-skills/creative/kanban-video-orchestrator/SKILL.md
  • optional-skills/creative/kanban-video-orchestrator/references/examples.md
  • optional-skills/creative/kanban-video-orchestrator/references/role-archetypes.md
  • optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md
  • optional-skills/creative/unreal-mcp/SKILL.md
  • optional-skills/creative/unreal-mcp/references/tool-surface.md
  • package.json
  • plugins/browser/browser_use/provider.py
  • plugins/kanban/dashboard/plugin_api.py
  • plugins/model-providers/kimi-coding/__init__.py
  • plugins/platforms/photon/sidecar/package.json
  • pyproject.toml
  • run_agent.py
  • scripts/ci/live_comment.py
  • scripts/ci/publish_e2e_evidence.py
  • scripts/whatsapp-bridge/package.json
  • skills/autonomous-ai-agents/merge-reconciler/SKILL.md
  • skills/devops/sdlc-review/SKILL.md
  • tests/ci/test_publish_e2e_evidence.py
  • tests/cli/test_cli_preloaded_skills.py
  • tests/gateway/test_api_server_multiplex_secret_scope.py
  • tests/gateway/test_config.py
  • tests/gateway/test_multiplex_adapter_registry.py
  • tests/gateway/test_multiplex_api_server_routing.py
  • tests/gateway/test_multiplex_busy_input_mode.py
  • tests/gateway/test_multiplex_http_routing.py
  • tests/gateway/test_multiplex_lifecycle.py
  • tests/gateway/test_multiplex_phase0.py
  • tests/gateway/test_profile_resolution.py
  • tests/gateway/test_relay_completion_injection_routing.py
  • tests/gateway/test_relay_delivery_followups.py
  • tests/gateway/test_relay_final_delivery_incident.py
  • tests/gateway/test_relay_injection_egress_priming.py
  • tests/gateway/test_relay_teardown_drain.py
  • tests/gateway/test_suppression_contract_matrix.py
  • tests/gateway/test_webhook_adapter.py
  • tests/hermes_cli/test_agent_env_advertisement.py
  • tests/hermes_cli/test_banner_skills.py
  • tests/hermes_cli/test_dashboard_admin_endpoints.py
  • tests/hermes_cli/test_gateway_service.py
  • tests/hermes_cli/test_honcho_monitor.py
  • tests/hermes_cli/test_kanban_notify.py
  • tests/hermes_cli/test_kanban_parent_reopen_invalidation.py
  • tests/hermes_cli/test_kanban_review_lifecycle.py
  • tests/hermes_cli/test_kanban_review_lifecycle_complete.py
  • tests/hermes_cli/test_kanban_review_surfaces.py
  • tests/hermes_cli/test_kanban_swarm.py
  • tests/hermes_cli/test_orphan_desktop_serve_reap.py
  • tests/hermes_cli/test_profile_export_credentials.py
  • tests/hermes_cli/test_profiles.py
  • tests/hermes_cli/test_serve_parent_watchdog.py
  • tests/hermes_cli/test_setup_menu_curses_migration.py
  • tests/hermes_cli/test_spawn_gateway_restart_reap.py
  • tests/hermes_cli/test_web_server.py
  • tests/plugins/browser/test_browser_provider_plugins.py
  • tests/plugins/test_kanban_dashboard_plugin.py
  • tests/skills/test_merge_reconciler_skill.py
  • tests/skills/test_sdlc_review_skill.py
  • tests/test_packaging_metadata.py
  • tests/test_resource_limits.py
  • tests/test_session_db_read_conn_pool.py
  • tests/test_session_db_read_path_split.py
  • tests/test_session_db_recall_reader.py
  • tests/tools/conftest.py
  • tests/tools/test_browser_use_cli.py
  • tests/tools/test_kanban_tools.py
  • tests/tools/test_mcp_server_log_notifications.py
  • tests/tools/test_read_binary_type_disclosure.py
  • tests/tools/test_restored_delegation_ownership.py
  • tests/tools/test_startup_latency_regressions.py
  • tools/async_delegation.py
  • tools/browser_tool.py
  • tools/browser_use_cli.py
  • tools/environments/base.py
  • tools/file_operations.py
  • tools/file_tools.py
  • tools/kanban_tools.py
  • tools/mcp_tool.py
  • tools/tts_tool.py
  • tools/vision_tools.py
  • toolsets.py
  • website/.npmrc
  • website/docs/developer-guide/contributing.md
  • website/docs/reference/cli-commands.md
  • website/docs/reference/environment-variables.md
  • website/docs/reference/optional-skills-catalog.md
  • website/docs/reference/tools-reference.md
  • website/docs/reference/toolsets-reference.md
  • website/docs/user-guide/configuration.md
  • website/docs/user-guide/features/browser.md
  • website/docs/user-guide/features/delegation.md
  • website/docs/user-guide/features/hooks.md
  • website/docs/user-guide/features/kanban-tutorial.md
  • website/docs/user-guide/features/kanban-worker-lanes.md
  • website/docs/user-guide/features/kanban.md
  • website/docs/user-guide/messaging/teams.md
  • website/docs/user-guide/multi-profile-gateways.md
  • website/docs/user-guide/skills/optional/creative/creative-blender-mcp.md
  • website/docs/user-guide/skills/optional/creative/creative-kanban-video-orchestrator.md
  • website/docs/user-guide/skills/optional/creative/creative-unreal-mcp.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/contributing.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/optional-skills-catalog.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/delegation.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban-tutorial.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban-worker-lanes.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/messaging/teams.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/optional/creative/creative-blender-mcp.md
  • website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/optional/creative/creative-kanban-video-orchestrator.md
  • website/package.json
  • website/sidebars.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

running on 86e3354 — Merge upstream/main into Stanislav's live fork

waiting for jobs to start…

@batumilove
batumilove force-pushed the sync/upstream-update-20260811T081410Z branch 3 times, most recently from b118081 to e3a0f52 Compare August 11, 2026 09:43
@batumilove

batumilove commented Aug 11, 2026

Copy link
Copy Markdown
Owner Author

Remediation update for exact candidate 86e3354:

  • removed unreachable legacy _ThreadReadConnectionLease and its removed _release_read_conn call
  • removed obsolete per-thread reader tests using _read_conns/direct permit ownership
  • replaced the remaining direct _get_read_conn concurrency test with simultaneous _read_ctx checkouts that return both connections and assert the pool contains both permits afterward
  • preserved bounded-pool tests and writer-lock/title-lineage convoy coverage
  • verification: Python compile pass; focused pool suite 4 passed / 17 environment WAL-gated skips; an isolated forced-pool contract probe passed; fork ownership 152 classified paths / 0 unexplained; diff check clean; exact remote SHA verified

Fresh exact-SHA review and hosted CI are running. The ci-reviewed gate remains intentionally closed pending PASS.

@batumilove
batumilove force-pushed the sync/upstream-update-20260811T081410Z branch from e3a0f52 to 86e3354 Compare August 11, 2026 09:52
@batumilove

Copy link
Copy Markdown
Owner Author

Independent review result for exact candidate 86e3354 (tree df1c4b2009cd4668673288a21b0fb25b1cdda107): verdict=PASS.

The reviewer verified:

  • all prior SessionDB merge blockers are removed
  • concurrent read checkouts use _read_ctx and return both pooled connections/permits
  • the post-close semaphore white-box test is valid and does not leak connections
  • 50 changed core Python files have no syntax errors, duplicate conflict artifacts, exact merge markers, or symbols common to both parents lost from the result
  • fork ownership: 152 classified paths, 0 unexplained
  • exact local and remote PR SHA match

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.

@batumilove batumilove added the ci-reviewed Maintainer reviewed CI-sensitive workflow/action changes label Aug 11, 2026
@batumilove
batumilove merged commit 242519f into batumi/live Aug 11, 2026
112 of 114 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-reviewed Maintainer reviewed CI-sensitive workflow/action changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.