Commit graph

15797 commits

Author SHA1 Message Date
giggling-ginger
6ff65c4d20 fix(gateway): scope default-listener api_server requests under multiplex
Rebuilt from PR #61283 onto the /p/<profile>/ routing world (7aa21e336):
_profile_scope(None) now enters the DEFAULT profile's runtime scope when
multiplexing is active instead of returning nullcontext(). api_server is
a port-binding platform living on the default profile, so plain requests
(no /p/ prefix) are the primary path — with fail-closed get_secret they
crashed with UnscopedSecretError on the first credential read (#61276).

All three wrapped call sites (chat-completions executor, /v1/runs agent
construction and _run_sync) inherit the fix through the one seam.
Single-profile gateways keep the no-op. Regression tests ported from
the original PR to the _profile_scope seam.

Fixes #61276
2026-07-16 07:17:55 -07:00
rlaehddus302
fef0b2d600 fix(gateway): scope secondary-adapter auth callback to its own profile
Subset of PR #61985: _make_adapter_auth_check gains a profile_name
parameter and secondary-profile adapters (started in
_start_one_profile_adapters) bind it, so the auth callback's
SessionSource resolves the routed profile's adapter and pairing store
instead of silently falling back to the default profile. This is the
gap left open by the #65629 merge — adapter-internal auth checks (e.g.
Slack thread-context fetch) fire outside the wrapped message handler.

The PR's authz_mixin.py hunks are dropped: main's _auth_env (merged via
PR #65629) already covers the scoped allowlist reads they targeted.
2026-07-16 07:17:55 -07:00
Teknium
0cc9426c6d test: feishu port-binding report expects webhook mode after #52563 integration
With the mode-conditional check centralized, default (websocket) Feishu
no longer counts as port-binding in the secondary batch report — pin the
fixture to connection_mode=webhook so the test still exercises the
multi-platform report path.
2026-07-16 07:17:55 -07:00
liuhao1024
9cb3569e97 fix(gateway): allow Feishu websocket mode in multiplex profiles
Feishu was unconditionally listed in _PORT_BINDING_PLATFORM_VALUES,
causing the multiplexer to reject ALL Feishu secondary profiles. But
Feishu in websocket mode (the default) uses an outbound WebSocket
connection and does NOT bind an HTTP port — only webhook/callback mode
needs a listener.

Add _platform_binds_port() helper that checks connection-dependent
platforms (currently only Feishu) against their actual config before
raising MultiplexConfigError. Feishu websocket profiles are now allowed;
Feishu webhook profiles still raise as before.

Fixes #52563
2026-07-16 07:17:55 -07:00
Jonny Kovacs
6b1267c2e4 fix(gateway): gate /profile source scoping on multiplex_profiles
Review follow-up: honor source.profile and enter _profile_runtime_scope
only when gateway.multiplex_profiles is on, mirroring the gating in
_run_agent, _reset_notice_session_info, and _resolve_profile_for_key.
When multiplexing is off (the default) a stamped source is ignored and
/profile reports the active profile and default home, byte-identical to
before this PR.

The stamped-source test now enables multiplexing (it previously
exercised the ungated path under the default config), and a new
regression asserts the stamp is ignored when multiplexing is off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 07:17:55 -07:00
Jonny Kovacs
f7d6f099db fix(gateway): /profile reports the profile serving the source, not the multiplexer's
On a multiplexed gateway the process-level active profile is always the
multiplexer's own (usually "default"), so /profile answered "default" in
every chat regardless of which profile actually served it — making
per-chat persona routing look broken when it was working.

Report source.profile (stamped by the /p/<profile>/ URL prefix, a
per-credential adapter, or a room->profile map) and resolve the
displayed home under that profile's runtime scope, mirroring the scoped
/reset banner (#59003). Unstamped sources fall back to the active
profile and default home, so single-profile gateways are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 07:17:55 -07:00
cresslank
8191f621c3 fix(gateway): preserve multiplex profile in model picker 2026-07-16 07:17:55 -07:00
Christopher
dd9e75335c fix(gateway): skip port-conflicting multiplex profiles 2026-07-16 07:17:55 -07:00
Teknium
fe2d847aca test: valid-shape Telegram token in port-binding guard fixture
The #62803 branch predates PR #64636's Telegram token-shape validation
on the messaging platform PUT endpoint; align the new guard test's
fixture with the validated format.
2026-07-16 07:17:55 -07:00
PRATHAMESH75
e984a61306 fix(dashboard): reject port-binding channels on secondary multiplexed profiles
The Channels API (PUT /api/messaging/platforms/{id}) accepted and persisted
enabling a port-binding platform on a secondary profile while
gateway.multiplex_profiles is on — a config the gateway only rejects on its
next start, aborting startup with MultiplexConfigError for every multiplexed
profile.

Validate before any .env/config.yaml write and return 409 for the enable
attempt. Disabling and clearing env stay allowed so an already-invalid
profile can be repaired. The port-binding platform set moves to
gateway/config.py (PORT_BINDING_PLATFORM_VALUES) as the single source of
truth shared by gateway startup validation and the dashboard, so the two
policies cannot drift. Platform config mutations now get a names-only audit
log line.

Fixes #62791
2026-07-16 07:17:55 -07:00
teknium1
3366204474 chore: AUTHOR_MAP entry for doxe0x (PR #50786 salvage) 2026-07-16 07:11:21 -07:00
teknium1
d34cc4093a fix(mcp): per-flow callback waiters so concurrent OAuth flows cannot cross ports
_wait_for_callback still read the legacy module-level _oauth_port, so
with two concurrent OAuth flows, flow A's callback wait bound flow B's
port while A's redirect URI pointed at A's port — the callback-side
half of the cross-flow collision that #65622 fixed on the redirect
side. _make_callback_waiter(port) closes over each flow's resolved
port; both provider construction sites (build_oauth_auth and
MCPOAuthManager._build_provider) now wire per-flow waiters. The legacy
_wait_for_callback delegates for backwards compatibility.

Direction credit to @LeonSGP43 (#34280) and the #34260 analysis.
2026-07-16 07:11:21 -07:00
doxe0x
454d553d34 fix(mcp): report a clear error when the OAuth callback port is in use
_wait_for_callback catches OSError on bind with a comment claiming the port is
held by a server build_oauth_auth started, and promising to fall back to polling
it. build_oauth_auth never starts a callback server (this is the only listener),
so there is nothing to poll: the branch just raised a misleading "OAuth callback
timed out" when the real cause is a busy port (a concurrent login, a leftover
listener, or a fixed oauth.redirect_port that collided).

Fix the stale comment and raise an accurate, actionable message (names the port,
suggests freeing it or setting a free oauth.redirect_port), chained from the
original OSError. Behavior is otherwise unchanged. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 07:11:21 -07:00
Teknium
bda8bd76a8
fix(api_server): mark port conflict as non-retryable to stop infinite reconnect loop (#65665)
A bare False from connect() on EADDRINUSE made the gateway reconnect
watcher treat a port conflict as transient and retry forever at the
backoff cap — 1568+ retries over 5 days in a multi-profile production
setup, filling errors.log and leaking 2 ResponseStore fds per retry.
Set a non-retryable fatal error (api_server_port_in_use) in the bind
OSError branch so the platform drops from the reconnect queue;
recover via /platform resume api_server after changing the port.

Re-implementation of #52132 by @msalles1 against the direct-bind path
from #65621 (the pre-probe block their patch targeted no longer
exists). Their production diagnosis and test scenario preserved.
2026-07-16 06:29:09 -07:00
Teknium
75c878217d fix(moa): route per-slot reasoning effort through the canonical parser
_clean_reasoning_effort kept its own whitelist that stopped at 'max',
silently dropping 'ultra' from MoA slot configs. Route it through
hermes_constants.parse_reasoning_effort — the same one-source-of-truth
fix the salvaged commit applies to the gateway — so future effort
levels can't drift here either. Docs updated to list ultra.

Follow-up to salvaged PR #64012.
2026-07-16 06:14:58 -07:00
Fli
4ad5036a44 fix(gateway): surface extended reasoning efforts 2026-07-16 06:14:58 -07:00
Dan Schnurbusch
a7a05024c1 fix(auth): key reentrancy by auth store path
Remove the dynamic active-store holder so a profile context switch cannot inherit another auth store's lock depth and skip its kernel lock.
2026-07-16 06:14:56 -07:00
Dan Schnurbusch
6ef13af4be fix(cron): preserve resolver call compatibility
Only fallback resolution needs an explicit target model. Keep the primary resolver call compatible with existing callers and test doubles while retaining atomic provider/model fallback selection.
2026-07-16 06:14:56 -07:00
Dan Schnurbusch
679487b807 fix(auth): enforce complete fallback routes
Skip provider-only setup fallbacks, keep fallback selection explicit for resumed sessions, preserve configured primary identity for cron drift checks, and make the auth lost-update regression deterministic.
2026-07-16 06:14:56 -07:00
Dan Schnurbusch
f68fd80f41 fix(auth): preserve fallback routes and OAuth state
Switch provider and model together after setup-time auth failure. Serialize global auth-store merges under target-specific locks and preserve auth-to-shared lock ordering for profile OAuth refreshes.
2026-07-16 06:14:56 -07:00
teknium1
261b0f8240 docs(mcp): document redirect_uri proxied callbacks + redirect_host WAF workaround
Adds the proxied-callback option to the remote/headless OAuth section,
links the mcp-oauth-remote-gateway skill for fully headless gateways,
and documents the WAF pitfall behind redirect_host.
2026-07-16 06:14:34 -07:00
teknium1
56e06d7ee9 chore(release): AUTHOR_MAP entries for Florian Burka (flewe) and Peter Skaronis (Peterskaronis) 2026-07-16 06:14:34 -07:00
teknium1
f01f0f75fe test(mcp-oauth): redirect_host coverage + adapt salvaged tests to the non-interactive guard
- redirect_host tests: localhost swap, precedence of full redirect_uri,
  empty-value fallback, client-metadata propagation
- The salvaged redirect-uri hint tests predate the #57836
  OAuthNonInteractiveError guard; monkeypatch _is_interactive like the
  sibling tests in TestRedirectHandlerSshHint
2026-07-16 06:14:34 -07:00
Peter Skaronis
dc419d6e80 mcp_oauth: configurable redirect_host (WAF-safe localhost redirect URIs)
Reclaim.ai's AWS API Gateway WAF 403s any /oauth2/authorize request whose
query string contains a literal 127.0.0.1, so the SDK's hardcoded
redirect_uri made the browser flow impossible. New optional oauth config
key redirect_host (default 127.0.0.1, unchanged behavior) lets a server
entry use localhost instead.

Integrated into _resolve_redirect_uri so it composes with redirect_uri:
an explicit redirect_uri wins; redirect_host only rewrites the loopback
default's hostname.
2026-07-16 06:14:34 -07:00
Florian Burka
d0afcb125c test(mcp-oauth): cover configurable redirect_uri + fix misleading SSH hint
The PR added a configurable `redirect_uri` (proxy/Funnel callbacks) but
shipped without tests, and the loopback SSH-tunnel hint stayed hardcoded —
actively misleading the exact proxy user the feature targets.

- Extract `_resolve_redirect_uri(cfg, port)` so the client-metadata and
  pre-registration paths derive an identical callback (a mismatch makes the
  authorization server reject the redirect).
- Make `_redirect_handler` redirect_uri-aware: a configured proxy callback
  reaches this machine on its own, so it no longer prints the `ssh -N -L`
  loopback guidance. Wired via `functools.partial` — no new global state.
- Document `redirect_uri` in the config block.
- 14 new tests (red/green TDD): helper resolution + empty-string fallback,
  metadata + pre-registration for configured/default, AnyUrl normalization,
  no-client_id skip, client_secret combo, and both SSH-hint branches.

ruff clean · 81 passed (tests/tools/test_mcp_oauth.py) · ty baseline unchanged

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 06:14:34 -07:00
Florian Burka
6297634d22 fix(mcp-oauth): allow configurable redirect_uri for MCP OAuth flows 2026-07-16 06:14:34 -07:00
Teknium
7a78342ab3 chore: add AUTHOR_MAP entry for shuangxinniao (PR #40127 salvage) 2026-07-16 05:56:22 -07:00
Teknium
a61e29e879 fix(pricing): refresh full DeepSeek snapshot to 2026-07 rates
Widens the deepseek-v4-flash addition to the whole stale-snapshot class:
- deepseek-v4-pro: $1.74/$3.48 → $0.435/$0.87, cache-read $0.003625
  (DeepSeek's 2026-07 price cut; every pro session was over-reporting 4x)
- deepseek-chat / deepseek-reasoner: deprecated 2026-07-24, now alias
  v4-flash non-thinking/thinking modes — repriced to match flash
  (reasoner was $0.55/$2.19 with no cache rate)
- cache_read added to every row; pricing_version unified at
  deepseek-pricing-2026-07
- invariant tests: aliases price identically to flash; every deepseek
  row carries cache_read < input
2026-07-16 05:56:22 -07:00
shuangxinniao
97397a1ccc feat(pricing): add deepseek-v4-flash to official-docs pricing snapshot
DeepSeek's /models endpoint returns no pricing, so direct-provider routes fall back to the _OFFICIAL_DOCS_PRICING snapshot. The table included deepseek-v4-pro but not the newer deepseek-v4-flash, so flash sessions reported $0.00 with cost_source "none". Add the flash entry (values from DeepSeek's official pricing page, mirroring the v4-pro entry; DeepSeek bills no separate cache-write cost) plus two regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 05:56:22 -07:00
nima20002000
53adb3fd97 feat(config): add get and unset commands 2026-07-16 05:44:43 -07:00
Teknium
5604d1852e chore(release): map aeyeopsdev noreply email in AUTHOR_MAP 2026-07-16 05:43:30 -07:00
aeyeopsdev
f61169861a fix(google-chat): allow http inbound without pubsub 2026-07-16 05:43:30 -07:00
Teknium
702473edbd chore(release): map jtstothard's email in AUTHOR_MAP (PR #63256 salvage) 2026-07-16 05:39:58 -07:00
Teknium
01d3268e02 fix(gateway): harden multiplex credential cluster salvage
Follow-ups on top of the cherry-picked cluster commits:

- slack: scope-authoritative app-token read — get_secret() with a
  narrow UnscopedSecretError fallback to os.getenv. Keeps @kohoj's
  correct semantics (scoped profile can never silently inherit the
  default profile's Socket Mode app) while fixing the regression where
  the default-profile startup loop and background reconnect rebuild,
  which call connect() unscoped under multiplex, would raise and
  fail-loop. Supersedes the 'or os.getenv' variant from #64461 which
  reintroduced the cross-profile fallback leak.
- test: unscoped-multiplex fallback regression test for connect().
- run.py: convert the last legacy self.adapters.get(source.platform)
  site (_rename_discord_auto_thread) to _adapter_for_source(source)
  so profile-routed Discord sources rename threads on the right
  adapter (from #57417's sweep).
- AUTHOR_MAP entry for @aguung.
2026-07-16 05:39:58 -07:00
Jay Stothard
64746b4bd3 fix(gateway): validate multiplex adapter config by platform 2026-07-16 05:39:58 -07:00
Jay Stothard
bd44ef8645 fix(gateway): restore multiplex secondary adapters
Partial cherry-pick of a7ffbbff7 from PR #63256: secondary-profile
adapter creation errors no longer abort the whole secondary startup
(try/except around _create_adapter + loud warning on None return), and
Home Assistant's check_ha_requirements() becomes dep-only with the
credential moved to a new validate_ha_config() so secondary profiles
whose HASS_TOKEN lives in the profile secret scope are not silently
dropped by the registry gate.

Telegram diagnostic hunks and profile-label stamping dropped: the
regression they targeted does not exist on current main and they
conflict with the connect() teardown fence.
2026-07-16 05:39:58 -07:00
Koho Zheng
ea2c9bc10f fix(slack): scope app token in multiplex gateway 2026-07-16 05:39:58 -07:00
liuhao1024
6160a80253 fix(gateway/platforms): migrate all Weixin fallbacks to get_secret() for consistent profile-scoped resolution
Per egilewski's security review, WEIXIN_BASE_URL and WEIXIN_CDN_BASE_URL
were still resolved from process-global environment variables, leaving
mixed-scope bypasses in multiplex mode.

Changed files:
- gateway/platforms/weixin.py: Added get_secret import, replaced os.getenv()
  with get_secret() for WEIXIN_ACCOUNT_ID, WEIXIN_TOKEN, WEIXIN_BASE_URL,
  WEIXIN_CDN_BASE_URL in WeixinAdapter.__init__() and send_weixin_direct()
- tools/send_message_tool.py: Added get_secret import, replaced os.getenv()
  with get_secret() for all WEIXIN_* fallbacks in _handle_send()

All runtime Weixin send paths now resolve both credentials and endpoint
configuration from the same profile-scoped source.
2026-07-16 05:39:58 -07:00
Agung Subastian
8fc989b416 fix(gateway): multiplex secret_scope for authz, Slack, webhooks
Secondary profiles under gateway multiplex keep tokens/allowlists in
profile secret_scope, not process os.environ. Auth and Slack were still
reading os.getenv, so Slack on a secondary profile failed allowlist and
socket mode. Webhook deliver also only looked at default adapters.

- Prefer get_secret for allowlists / allow-all flags (authz_mixin)
- Slack app token + allowlist via secret_scope with getenv fallback
- Wrap secondary profile message handlers in _profile_runtime_scope
  before auth runs
- Resolve home-channel env from secret_scope / PlatformConfig
- Webhook deliver falls back to _profile_adapters for target platform
- Template key event_type for webhook prompts
2026-07-16 05:39:58 -07:00
teknium1
c82a196ea9 chore: AUTHOR_MAP entry for Code-suphub's second commit email (PR #44872 salvage) 2026-07-16 05:39:52 -07:00
teknium1
49d3fee0bd chore: AUTHOR_MAP entry for Code-suphub (PR #44872 salvage) 2026-07-16 05:39:52 -07:00
teknium1
95a0f9c836 fix(mcp): close select-to-bind TOCTOU on the OAuth callback port
_find_free_port() closed its probe socket before HTTPServer re-bound
the port minutes later, leaving a window where another process could
steal it (#22161 by @amathxbt). _reserve_callback_port() now keeps the
selected socket bound (bounded FIFO pool) until _wait_for_callback
adopts it via bind_and_activate=False. Also sets allow_reuse_address
BEFORE binding — the cherry-picked #44872 set it after the constructor
had already bound, where it is a no-op.

Also updates the three #57836 non-interactive-guard tests to the
closure-factory API from #44872.
2026-07-16 05:39:52 -07:00
XuefeiLi
f4c7caa70c fix(mcp): remove unreachable dead code after return in _make_redirect_handler 2026-07-16 05:39:52 -07:00
Code-suphub
13e19a9092 fix(mcp): use per-provider closures and allow_reuse_address for OAuth (#44588, #44590)
Two related OAuth fixes:

1. Replace module-level _redirect_handler with _make_redirect_handler()
   closure factory that closes over the resolved port. This prevents
   cross-server state pollution when multiple MCP servers run OAuth
   concurrently (#44588).

2. Set server.allow_reuse_address = True on the ephemeral callback
   HTTPServer so the socket doesn't stay in TIME_WAIT after the flow
   completes. This prevents 'Address already in use' errors on the
   next OAuth flow for the same port (#44590).

Fixes #44588
Fixes #44590
2026-07-16 05:39:52 -07:00
Teknium
164bca658e
fix(gateway): bind api_server directly instead of pre-probing 127.0.0.1 (#65621)
The single-family pre-probe (_port_is_available) raced the real bind and
reported a lingering TIME_WAIT socket as 'in use', failing gateway
restarts for up to ~60s (#10297). Port the webhook adapter's bind
mechanics (#63711/#65482): delete the probe, bind directly with clean
OSError handling and runner teardown, and scope reuse_address=False to
macOS only so Linux restarts rebind past TIME_WAIT instantly.

Credit to @lrawnsley (#10297) for identifying the TIME_WAIT restart
failure.
2026-07-16 05:39:43 -07:00
Teknium
21dedb8586
fix(insights): include auxiliary usage in overview token totals (#65603)
The overview's total_input/output/cache token counts summed only the
sessions counters (main-loop usage), while the per-model breakdown
already included auxiliary usage rows (task dimension from #65537) and
reconciled residuals. Result: hermes insights top-line totals
undercounted aux spend (compression summarizer, vision, titles) and
disagreed with the per-model table below them — the symptom reported
in #58592 and requested in #9979.

When the per-model breakdown is available, derive the overview token
totals from it (same pattern total_cost already used). Verified no
double-count across incremental CLI deltas, gateway absolute
overwrites, and aux rows.
2026-07-16 05:39:33 -07:00
Teknium
07e537d8ea chore: AUTHOR_MAP entry for salvaged PR #65105 2026-07-16 05:08:56 -07:00
embwl0x
bfb51fec81 docs(gateway): document external restart contract 2026-07-16 05:08:56 -07:00
embwl0x
caf5f27e30 fix(gateway): preserve external supervisor ownership 2026-07-16 05:08:56 -07:00
Siddharth Balyan
7d8c499893
fix(desktop): preserve node-pty helper in packaged app (#65611)
Guard staged node-pty ASAR path rewrites so already-unpacked paths are
not rewritten twice. Normalize spawn-helper to mode 0755 in both the
prebuild and locally compiled build/Release staging paths.

Add behavioral coverage for both unpacked path forms and both helper
layouts.

Co-authored-by: zhouwei <zwcf5200@163.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-07-16 11:58:50 +00:00