Commit graph

8616 commits

Author SHA1 Message Date
EloquentBrush0x
ad00777f04 fix(mcp-oauth): print SSH tunnel hint in _redirect_handler
When Hermes runs on a remote host over SSH, MCP OAuth loopback flows
silently fail: the OAuth provider redirects the user's browser to
http://127.0.0.1:<port>/callback, which reaches the callback server
on the *remote* machine — not the local machine where the browser is
running.

_redirect_handler already detected SSH (via _can_open_browser) and
printed "Headless environment detected — open the URL manually." but
gave no guidance on how to actually reach the callback server. Users
got silent timeouts or "Could not establish connection" errors.

This is the same bug fixed for xAI-oauth and Spotify in #26592, which
added _print_loopback_ssh_hint() in hermes_cli/auth.py. mcp_oauth.py
uses the identical loopback callback pattern (http://127.0.0.1:<port>/callback
via _configure_callback_port / _wait_for_callback) but was missing the hint.

Fix: when SSH_CLIENT or SSH_TTY is set and _oauth_port is available,
print the ssh -N -L port-forward command and the OAuth-over-SSH guide
URL to stderr, consistent with the rest of _redirect_handler's output.

Tests: 4 new cases in TestRedirectHandlerSshHint covering SSH_CLIENT,
SSH_TTY, local session (no hint), and missing _oauth_port (no hint).
2026-05-17 02:29:37 -07:00
teknium1
cc59880ab0 chore(release): map EloquentBrush0x email for #26642 salvage 2026-05-17 02:21:06 -07:00
EloquentBrush0x
a9ba636d53 fix(tools): run post_setup in _reconfigure_provider() for env-var providers
_configure_provider() calls _run_post_setup() after collecting env vars
(line 2286). _reconfigure_provider() did not — providers with both
env_vars and post_setup (Browserbase, Browser Use, Firecrawl, Camofox)
skipped the installation step on reconfiguration.

Fix: mirror the _configure_provider() call. post_setup hooks are
idempotent (check before installing), so no behaviour change for users
who already have the dependencies installed.
2026-05-17 02:21:06 -07:00
Teknium
ad1aa1a037
feat(x_search): auto-enable toolset when xAI OAuth or XAI_API_KEY is configured (#27376)
The x_search toolset is gated on xAI credentials (SuperGrok OAuth or
XAI_API_KEY), but it was staying off-by-default even for users who had
already configured those credentials — they had to also click through
`hermes tools` → X (Twitter) Search to flip it on. The HASS_TOKEN →
homeassistant rule already handles the parallel case cleanly; x_search
needs the same treatment.

Why a separate code path from HASS_TOKEN: `ha_*` tools live inside
the `hermes-cli` composite, so the subset-inference loop picks them
up and the HASS branch just unmasks default_off. `x_search` is its
own one-tool toolset NOT in the composite, so the subset loop never
adds it — it has to be injected directly.

* Add `_xai_credentials_present()` — side-effect-free check for stored
  xAI OAuth tokens or XAI_API_KEY (dotenv or env). No network.
* In `_get_platform_tools()` else branch (no explicit user config),
  inject `x_search` and carve a parallel hole in default_off.
* Auto-enable does NOT fire when the user has saved an explicit toolset
  list via `hermes tools` — that list stays authoritative.
* `agent.disabled_toolsets: [x_search]` still wins (global override).

Tests: 4 new in test_tools_config.py covering OAuth path, API-key path,
no-creds path, and explicit-config-respect. All pass alongside existing
70/70 in that file.
2026-05-17 02:19:38 -07:00
kshitij
519657aa98
fix(matrix): warn on clock-skew silent message drops (#12614) (#27330)
The 5-second startup-grace filter in _on_room_message silently drops
events where event_ts < startup_ts - 5. When the host clock is set
ahead of real time, the comparison flips against every live event and
the bot 'connects but never replies' — exactly the symptom in #12614.

Reporter Schnurzel700 chased this for several weeks before tracing it
to their Debian VM's clock being out of sync. The current /1000.0
millisecond->second conversion is correct (mautrix returns ms); the
failure mode is purely environmental.

Add a one-shot WARNING that fires when:
  - we are >30s past startup (initial-sync replay window closed), AND
  - 3 consecutive drops share the same skew within 60s (a constant
    clock offset, not varied-age backfill from an invited room).

State is reset in connect() so reconnects after fixing NTP rearm the
detector. Includes the NTP fix instruction in the warning message
itself and a new Troubleshooting entry in the Matrix docs.

5 new tests cover the happy path, initial-sync backfill, under-
threshold drops, varied-age backfill, and the reconnect rearm path.
2026-05-17 00:28:24 -07:00
Teknium
56ad30de17
Merge pull request #27248 from NousResearch/hermes/hermes-27dc9cc2
refactor(run_agent): extract AIAgent internals into agent/ modules (16k→3.8k lines, 76% reduction)
2026-05-16 23:52:16 -07:00
teknium1
563b4d9e51
fix: strip image parts for non-vision models with provider profiles + getattr-safe _custom_providers
Original commit 75e5d0f6b by hueilau targeted _build_api_kwargs in
pre-refactor run_agent.py. The body now lives in
agent/chat_completion_helpers.build_api_kwargs — re-applied there.

Also: switch the custom_providers forward (from 21078ebce) to use
getattr() — tests build a bare AIAgent via __new__ and would otherwise
hit AttributeError on _custom_providers.

Co-authored-by: hueilau <33933019+hueilau@users.noreply.github.com>
2026-05-16 23:47:51 -07:00
teknium1
36ad8336f9
fix(run_agent): guard memory provider init against empty/whitespace string
Original commit 8d756a421 by austrian_guy targeted __init__ in
pre-refactor run_agent.py. The body now lives in
agent/agent_init.init_agent — re-applied there.

Co-authored-by: austrian_guy <33156212+ether-btc@users.noreply.github.com>
2026-05-16 23:43:09 -07:00
teknium1
4ece521bcf
fix(run_agent): isolate background review fork from external memory plugins (#27190)
Original commit 973f27e95 by Teknium targeted _spawn_background_review in
pre-refactor run_agent.py. The body now lives in
agent/background_review._spawn_background_review — re-applied there.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:42:49 -07:00
teknium1
b5bcffe167
fix(fallback): forward custom_providers to fallback model context-length detection
Original commit 21078ebce by PaTTeeL targeted _try_activate_fallback in
pre-refactor run_agent.py. The body now lives in
agent/chat_completion_helpers.try_activate_fallback — re-applied there.

Co-authored-by: PaTTeeL <9150277+PaTTeeL@users.noreply.github.com>
2026-05-16 23:42:16 -07:00
teknium1
4ab9a06a51
fix(agent): reset _fallback_index at turn start even when no fallback activated
Original commit 33528b428 by konsisumer targeted _restore_primary_runtime
in pre-refactor run_agent.py. The body now lives in
agent/agent_runtime_helpers.restore_primary_runtime — re-applied there.

Fixes #20465

Co-authored-by: konsisumer <der@konsi.org>
2026-05-16 23:41:45 -07:00
teknium1
aa05ffba53
fix(xai): surface provider 'error' SSE frame in Codex fallback stream (#27184)
Original commit 2b193907d by Teknium added a new module-level
_StreamErrorEvent class and threaded its raise into
_run_codex_create_stream_fallback in pre-refactor run_agent.py.

  - _StreamErrorEvent class → run_agent.py (module-level, next to
    _qwen_portal_headers; class needs to be top-level for the codex
    runtime to import it)
  - The fallback event-loop's 'type=error' handler → agent/codex_runtime.py
    where run_codex_create_stream_fallback now lives. Imports
    _StreamErrorEvent lazily from run_agent to avoid circular import.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:41:09 -07:00
teknium1
80fa92a491
fix(codex): rotate pool on usage limit 429 — port to extracted modules
Original commit e51d74ab9 by Maxim Esipov targeted _extract_api_error_context
and _recover_with_credential_pool in pre-refactor run_agent.py. Both bodies
now live in agent/agent_runtime_helpers.py — re-applied to that module:

  - extract_api_error_context: payload.get('type') added to the reason
    fallback chain (Codex error bodies use 'type' instead of 'code'/'error')
  - recover_with_credential_pool: usage_limit_reached detection in the
    rate_limit branch — skip the retry-once-then-rotate dance and rotate
    immediately when the body says the per-account usage limit hit.

Co-authored-by: Maxim Esipov <maksesipov@gmail.com>
2026-05-16 23:39:41 -07:00
teknium1
df22d29522
fix(copilot): GitHub Models 413 hint — port to extracted conversation_loop
Original commits 4ded3ede3 (@konsisumer) + 374dc81c2 (Teknium) added a
413 hint to run_agent.py's agent loop. Final-state version (the sharpened
374dc81c2 wording) ported to agent/conversation_loop.py, where the
payload_too_large branch now lives.

The deprecation detection + _URL_TO_PROVIDER changes from both commits
landed in agent/copilot_acp_client.py and agent/model_metadata.py via
the prior merge.

Closes #10648

Co-authored-by: konsisumer <der@konsi.org>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:38:45 -07:00
teknium1
3fbedd732e
feat: add supports_parallel_tool_calls for MCP servers (#26825) — port to tool_dispatch_helpers
Original commit 395e9dd9e by Teknium targeted module-level _is_mcp_tool_parallel_safe
and _should_parallelize_tool_batch helpers in pre-refactor run_agent.py. Both
helpers now live in agent/tool_dispatch_helpers.py — re-applied to that
module.

The tools/mcp_tool.py portion (the public is_mcp_tool_parallel_safe API
+ _parallel_safe_servers tracking) merged cleanly from main via the prior
merge commit.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:36:37 -07:00
teknium1
fe4c87eb28
fix(agent): retry malformed anthropic stream parser errors — port to extracted modules
Original commit 9c304a7f5 by helix4u targeted _flatten_exception_chain,
_summarize_api_error, and the _call streaming retry loop in pre-refactor
run_agent.py. Re-applied to:

  - New _is_provider_stream_parse_error helper → run_agent.py (next
    to _flatten_exception_chain in the AIAgent class)
  - _summarize_api_error early-return for the malformed-streaming
    ValueError → run_agent.py (kept method body)
  - _call streaming retry: _is_stream_parse_err flag wired into
    _is_transient AND the post-exhaustion branch + dedicated
    malformed-streaming user-status string → agent/chat_completion_helpers.py
    (the _call body now lives there)

Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>
2026-05-16 23:35:54 -07:00
teknium1
f885be030c
fix(auxiliary): resolve xai oauth compression from pool — port to conversation_compression
Original commit 97a32afdc by helix4u targeted _check_compression_model_feasibility
in pre-refactor run_agent.py. The function body now lives in
agent/conversation_compression.py — re-applied the configured-but-unavailable
provider message there.

Co-authored-by: helix4u <4317663+helix4u@users.noreply.github.com>
2026-05-16 23:33:59 -07:00
teknium1
6975a2d9ae
fix(xai-oauth): entitlement-403 chain — final state (ce0e189d3 + 9818b9a1a + 6784c8079 + dffb602f3)
Collapses the four-commit xAI entitlement-403 chain to its final
on-main state, ported to the post-refactor module layout:

  - Added _is_entitlement_failure on AIAgent (run_agent.py) — detects
    Grok subscription-shape 403s on (401|403|None) status codes.
  - Added entitlement-skip branch to recover_with_credential_pool
    (agent/agent_runtime_helpers.py) — breaks the refresh-loop that
    Don's 100-iteration trace exposed when a Premium+ user hit a real
    entitlement issue.
  - Removed _decorate_xai_entitlement_error and unwrapped its two
    _summarize_api_error call sites — xAI's own body text already
    points users at grok.com/?_s=usage so we surface that verbatim
    (dffb602f3 reasoning: X Premium subs DO now work per xAI's
    2026-05-16 announcement, so editorialising would misdirect).
  - grok-4.3 1M context entry landed in agent/model_metadata.py
    via the prior merge — no additional port needed.

Tests already on disk (tests/run_agent/test_codex_xai_oauth_recovery.py)
assert _is_entitlement_failure shape and verbatim body surfacing.

Closes #27110.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:33:18 -07:00
teknium1
408aa4fbc4
port(refactor): deepseek thinking-mode (068c24f8a + cd9470f41) — no net change
The original 068c24f8a (DeepSeek thinking via legacy chat_completions path)
was reverted by cd9470f41 (rewired to DeepSeekProfile.build_api_kwargs_extras).
Both commits' run_agent.py edits cancel out at the extracted-module level.
The active fix lives in plugins/model-providers/deepseek/__init__.py
(merged cleanly from main via the prior merge commit).

Co-authored-by: twebefy <twebefy@gmail.com>
Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:30:37 -07:00
teknium1
6362e71973
fix(xai-oauth): recover from prelude SSE errors, gate reasoning replay, surface entitlement 403s
Original commit 31ba2b0cb by Teknium targeted run_codex_stream() at
its pre-refactor location in run_agent.py. Re-applied:

  - Prelude error retry/fallback → agent/codex_runtime.py (in
    run_codex_stream where the body now lives)
  - _decorate_xai_entitlement_error helper + _summarize_api_error
    wrapping → run_agent.py (these methods remained on AIAgent
    as @staticmethod's; cherry-pick applied them cleanly)

The xai-oauth provider gate, encrypted_content drop on replay, etc.
landed in agent/codex_responses_adapter.py via the prior merge from main.

Closes #8133, #14634

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-05-16 23:28:05 -07:00
teknium1
27df249564
feat(nvidia): add NIM billing origin header — port to extracted modules
Original commit 13c3d4b4e by kchantharuan touched __init__ and
_apply_client_headers_for_base_url in pre-refactor run_agent.py. Re-applied to:

  - __init__: agent/agent_init.py (3 hunks — NVIDIA branch + _custom_headers
    fallback in routed-client and fallback-client paths)
  - _apply_client_headers_for_base_url: still in run_agent.py (1 hunk)

build_nvidia_nim_headers was already present in agent/auxiliary_client.py
from the prior merge — no additional port needed.

Co-authored-by: kchantharuan <kchantharuan@nvidia.com>
2026-05-16 23:25:11 -07:00
teknium1
b07524e53a
feat(xai-oauth): add xAI Grok OAuth (SuperGrok Subscription) provider — port to extracted modules
Original commit b62c99797 by Jaaneek targeted six locations in
pre-refactor run_agent.py. Re-applied to the extracted post-PR locations:

  - api_mode dispatch → agent/agent_init.py
  - is_xai_responses build_api_kwargs → agent/chat_completion_helpers.py
  - codex_auth_retry block + 401 hint → agent/conversation_loop.py
  - _try_refresh_codex_client_credentials body → run_agent.py (kept)

The non-run_agent.py portions of the commit (auxiliary_client, codex
transport, hermes_cli/auth, tools/xai_http, tests, docs) merged cleanly
from main via the prior merge commit.

Co-authored-by: Jaaneek <Jaaneek@users.noreply.github.com>
2026-05-16 23:23:38 -07:00
teknium1
7d221aa1f2
fix(langfuse): complete observability fix — port to extracted conversation_loop
Original commit db84a78e6 by kshitij targeted run_conversation()'s
pre_api_request and post_api_request hooks in pre-refactor run_agent.py.
Re-applied to the extracted location in agent/conversation_loop.py.

Co-authored-by: kshitij <82637225+kshitijk4poor@users.noreply.github.com>
Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
Co-authored-by: Brian Conklin <brian@dralth.com>
2026-05-16 23:21:51 -07:00
teknium1
a77ca9295e
perf(run_agent): accumulate length-continuation prefix via list+join
Original commit 4f8aaf104 by InB4DevOps targeted run_conversation() in
the pre-refactor run_agent.py. Re-applied to the extracted location in
agent/conversation_loop.py.

Co-authored-by: InB4DevOps <tolle.lege+github@gmail.com>
2026-05-16 23:20:27 -07:00
hermesagent26
94b3131be7
fix(run_agent): detect kimi models via model name for reasoning pad
previously only checked provider ID and
base URL. When kimi-k2.6 is served via ollama-cloud (or any third-party
provider), provider is not 'kimi-coding' and base URL is not
api.kimi.com — so reasoning_content pad was never injected. This caused
HTTP 400 from Ollama Cloud's Go backend: 'invalid message content type:
map[string]interface {}'.

Fix: add model-name detection ('kimi' in model.lower()) so any route
serving a kimi model gets the required reasoning_content echo-back.

Refs the 400/401 Telegram errors where kimi-k2.6 via ollama-cloud
consistently failed after tool-call turns.

(cherry picked from commit 9a9f8a6d99)
2026-05-16 23:19:17 -07:00
Matthew Lai
8f3bc17db9
feat(agent): Added gemma 4 to reasoning allowlist
(cherry picked from commit 7244116b68)
2026-05-16 23:19:17 -07:00
teknium1
152d42d1a7
Merge origin/main into pr-27248 (resolving run_agent.py = ours)
run_agent.py taken from HEAD (the extracted forwarder structure). The 25
run_agent.py fixes that landed on main during the PR's life need to be
ported into the agent/* extracted modules in follow-up commits.
2026-05-16 23:16:52 -07:00
teknium1
7322816efa chore(release): AUTHOR_MAP entries for batch salvage group 4 contributors
Adds release-note attribution mappings for 9 contributors from group 4:
- @EloquentBrush0x (PR #26657)
- @subtract0 (PR #25658)
- @zwolniony (PR #26961)
- @that-ambuj (PR #26582)
- @zccyman (PR #25294)
- @lidge-jun (PR #26814)
- @phoenixshen (PR #26768)
- @AhmetArif0 (PR #26635)
- (francip already mapped from prior PR #26134 attribution)

#27147 dropped from this batch — already landed on main as 4b17c2411.
2026-05-16 23:11:43 -07:00
AhmetArif0
35b7befc67 fix(line): add trust_env=True to all _LineClient aiohttp sessions
_LineClient's five aiohttp.ClientSession() calls omit trust_env=True,
silently bypassing HTTP_PROXY / HTTPS_PROXY / ALL_PROXY. Result: every
LINE API call (reply, push, loading, fetch_content, get_bot_user_id)
ignores the system proxy.

Fix: add trust_env=True to all five session constructions. Symmetric
with the wecom and weixin adapters which already set this flag. No
behavior change for users not behind a proxy.
2026-05-16 23:11:43 -07:00
phoenixshen
52c89715a2 fix: respect user-configured vision model for OpenRouter
_OPENROUTER_MODEL hardcoded 'google/gemini-3-flash-preview' which
returns 404 on OpenRouter, breaking all vision tasks for users who
rely on the OpenRouter default.  Additionally, _try_openrouter()
ignored the user-configured auxiliary.vision.model entirely.

Changes:
- Update _OPENROUTER_MODEL default to google/gemini-2.5-flash (valid)
- Add optional 'model' parameter to _try_openrouter()
- Pass configured model from _resolve_strict_vision_backend() through
  to _try_openrouter()

This allows users who set auxiliary.vision.model (e.g. x-ai/grok-4.3)
to have it actually used, while maintaining backward compatibility.
2026-05-16 23:11:43 -07:00
bitkyc08-arch
5631345b12 [agent] fix: harden api server response headers 2026-05-16 23:11:43 -07:00
zccyman
b389796ae3 fix(auxiliary): resolve api_key_env alias in named custom provider path of resolve_provider_client
In resolve_provider_client(), the named custom provider code path at
~line 2914 only checked the ``key_env`` field when looking for an
environment-variable-based API key. The documented ``api_key_env``
snake_case alias was silently ignored, causing custom providers
configured with ``api_key_env`` to fall through to the
``no-key-required`` placeholder — which produces a confusing 401
(``****ired`` mask) on auth-required remote endpoints.

This mirrors the same fix already applied to run_agent.py in commit
6ddc48b05 (fix(fallback): resolve api_key_env in fallback chain entries).

Also adds a logger.warning() when the placeholder is reached, so
future alias gaps are easier to debug.

Closes #25091
2026-05-16 23:11:43 -07:00
Franci Penov
0afab4a32b feat(gateway): extract auto-TTS markdown strip into prepare_tts_text() hook
Refactor the inlined `re.sub(...)[:4000].strip()` cleanup at the
auto-TTS site in `_process_message_background` into an overridable
method `BasePlatformAdapter.prepare_tts_text(text: str) -> str`.

The default implementation is byte-identical to the previous inline
expression — strip `* _ \` # [ ] ( )` and truncate to 4000 chars — so
every existing adapter (Telegram, Discord, Slack, Matrix, IRC, etc.)
gets exactly the same behaviour as before. Zero behaviour change for
any consumer that doesn't override the method.

Why add the hook: voice-first platform adapters need stricter
cleanup than text-bubble platforms. The default strips a handful of
markdown sigils, which is fine when the output goes into a Discord
embed or a Telegram message bubble — but read aloud by a TTS engine,
URLs (`https://example.com/foo`), fenced code blocks, file paths
(`/Users/x/foo.py`), and `MEDIA:` tags turn into long sequences of
unintelligible characters. With this hook an adapter can drop those
spans before TTS while leaving the data-channel transcript intact
for visual rendering.

Without the hook, voice adapters have to either
  - duplicate the auto-TTS flow inside their own `handle_response`
    pipeline, which means re-implementing the entire `extract_media`,
    `extract_images`, `extract_local_files`, attachment routing and
    error-handling sequence in `_process_message_background`, or
  - live with TTS speaking URLs character-by-character.

Both are worse than a 7-line method addition.

Example consumer:
  https://github.com/kortexa-ai/hermes-livekit — LiveKit WebRTC voice
  gateway plugin. Its `LiveKitAdapter.prepare_tts_text()` additionally
  strips fenced code blocks, inline code, URLs, file paths, and
  `MEDIA:` tags before TTS synthesis, while the full response still
  reaches connected clients via the data channel. Drop-in installable
  via `pip install git+https://github.com/kortexa-ai/hermes-livekit.git`.

Carved out of #3894 (LiveKit WebRTC gateway PR) so the generic hook
can land independently of the LiveKit platform itself.
2026-05-16 23:11:43 -07:00
Ambuj Kumar
a3017508bf fix(gateway): preserve underscores in plain-text identifiers 2026-05-16 23:11:43 -07:00
zwolniony
364a1dd290 Local: doctor uses x-goog-api-key for Google generativelanguage endpoint 2026-05-16 23:11:43 -07:00
subtract0
fdd455bc58 fix(gateway): avoid zsh status variable in update wrapper 2026-05-16 23:11:43 -07:00
EloquentBrush0x
c1ae18ee81 fix(gateway): add trust_env=True to aiohttp sessions in SMS, Slack, Teams, Google Chat adapters
aiohttp.ClientSession defaults to trust_env=False, which silently ignores
HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY environment variables. Users behind
a corporate or network proxy cannot reach external APIs on any of these
platforms — all outbound requests fail with connection errors.

Symmetric with wecom.py (line 276), weixin.py (lines 1055/1268/1274), and
matrix.py (no-proxy path) which already set this flag. Complements the
open LINE fix (#26635) with the remaining gateway and plugin adapters.

Changed:
- gateway/platforms/sms.py: persistent Twilio session (connect) + fallback
  session (send) — both hit https://api.twilio.com
- gateway/platforms/slack.py: ephemeral response_url POST session —
  hits https://hooks.slack.com/... callback URLs
- plugins/platforms/teams/adapter.py: standalone send session —
  hits login.microsoftonline.com (token) + Bot Framework service URL
- plugins/platforms/google_chat/adapter.py: standalone send session —
  hits https://chat.googleapis.com/v1/...

WhatsApp sessions are excluded: they connect to http://127.0.0.1:{port}
(local bridge) and must not be routed through a system proxy.
2026-05-16 23:11:43 -07:00
teknium1
04bb30730a chore(release): AUTHOR_MAP entries for batch salvage group 3 contributors
Adds release-note attribution mappings for 9 contributors from group 3:
- @darvsum (PR #26766)
- @hueilau (PR #26498)
- @Timur00Kh (PR #27114)
- @Grogger (PR #27061)
- @lemassykoi (PR #27042)
- @draplater (PR #26707)
- @pr7426 (PR #27048)
- @therahul-yo (PR #26215)
- @flamiinngo (PR #27205)

#27154 dropped from this batch — already landed on main as 4e9cedcd4.
2026-05-16 23:05:27 -07:00
flamiinngo
8973b00ff3 fix(scripts): fix UnicodeEncodeError in footgun checker on Windows
The check-windows-footguns.py script outputs a checkmark (U+2713) and
cross (U+2717) to report results. Windows terminals default to cp1252,
which cannot encode these characters, so running the script on Windows
threw a UnicodeEncodeError before any results were printed.

This made the tool completely unusable on the exact platform it exists
to help -- a developer on Windows trying to check their code for
Windows-safety issues would just get a crash instead.

Fix: reconfigure stdout and stderr to UTF-8 at the start of main(),
before any output is produced. Verified on Windows 11 Home with
Python 3.13 (terminal defaulting to cp1252).
2026-05-16 23:05:27 -07:00
Rahul
a52f014a8c fix(tests): mock keychain in TestReadClaudeCodeCredentials to prevent credential leakage
Tests in TestReadClaudeCodeCredentials were not mocking
_read_claude_code_credentials_from_keychain, which was added after the
tests were written. On macOS machines with real Claude Code credentials
stored in the Keychain, the function returns live credentials instead of
the test fixtures, causing assertions to fail and leaking real tokens in
test output.

Add an autouse fixture that stubs the keychain reader to None so all
tests in the class exercise only the file-based credential path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 23:05:27 -07:00
pr7426
7a7e78a360 fix(cron): prevent parallel job result loss on exception
Replace generator-based result collection with explicit per-future
handling. Each future is now processed independently with a 600s timeout.

Before: _results.extend(f.result() for f in _futures)
- One exception stops the generator, remaining results are lost
- No timeout: one hung job blocks the entire tick

After: as_completed() + per-future try/except
- Each future handled independently
- 600s timeout prevents indefinite blocking
- Failed futures are logged and counted as failures
2026-05-16 23:05:27 -07:00
draplater
6158964ff6 feat: inject current time into goal judge prompt
The goal judge only receives the goal text and the agent's last
response. It has no concept of the current time, making it
impossible to evaluate time-sensitive goals like 'keep working
until 5pm'.

This commit adds 'Current time' to both JUDGE_USER_PROMPT_TEMPLATE
and JUDGE_USER_PROMPT_WITH_SUBGOALS_TEMPLATE, computed from
datetime.now().astimezone() at judge call time.
2026-05-16 23:05:27 -07:00
lemassykoi
6f50c26b2a fix(model-switch): probe /models for custom providers without api_key
The Telegram/Discord model picker skipped live model discovery for
custom providers (llama.cpp, Ollama) unless an api_key was configured.
Local providers typically don't require auth on the /models endpoint.

The CLI always probes /models, so this brings the gateway picker into
parity.

Change: `if api_url and api_key:` -> `if api_url:`
2026-05-16 23:05:27 -07:00
Grogger
8bf09455dc fix(windows): suppress console window flash on subprocess spawns
Add creationflags=CREATE_NO_WINDOW to every Windows Popen call
across the terminal, process registry, code execution, and kanban
worker subsystems. Prevents visible CMD windows from flashing on
the user's desktop during agent operation.

Also adds the _IS_WINDOWS module constant to kanban_db.py where
it was missing, for consistency with the other patched files.

5 Popen sites across 4 files:
- tools/environments/local.py (terminal foreground spawn)
- tools/process_registry.py (background process spawn)
- tools/code_execution_tool.py (sandbox + interpreter probe)
- hermes_cli/kanban_db.py (kanban worker spawn)
2026-05-16 23:05:27 -07:00
Timur00Kh
5338250dab fix(gateway): add direct_messages_topic_id for synthetic Telegram DM events
When /goal loop generates synthetic MessageEvents (goal continuations,
status notices), the reply anchor is unavailable (message_id=None). For
Telegram DM topic lanes, the Telegram adapter requires
direct_messages_topic_id to route messages correctly; without it, the
adapter falls back to message_thread_id=None, sending messages to the
root 'All Messages' thread instead of the active topic lane.

The fix includes direct_messages_topic_id in thread metadata for all
non-General Telegram DM topics, ensuring queued/synthetic messages are
delivered to the correct thread even when no reply anchor exists.
2026-05-16 23:05:27 -07:00
hueilau
75e5d0f6bd fix: strip image parts for non-vision models with provider profiles
_propare_messages_for_non_vision_model() was only called in the legacy
flag path (no provider profile). Providers with registered profiles
(e.g. DeepSeek, Kimi) bypassed the strip, causing HTTP 400 errors when
image_url content blocks reached their non-vision APIs.

This mirrors the existing behavior in the legacy path, ensuring all
non-vision models get image stripping regardless of profile status.
Vision-capable models are unaffected (the function is a no-op for them).
2026-05-16 23:05:27 -07:00
darvsum
bde3c7982c fix: preserve discover_models in _normalize_custom_provider_entry
The _normalize_custom_provider_entry() function was dropping the
discover_models field from custom_provider entries because:

1. It was not listed in _KNOWN_KEYS, so it was logged as an
   unknown key and ignored.
2. The function builds the normalized dict by explicitly copying
   known fields, so even if the warning was suppressed, the value
   was not carried through.

This caused downstream model_switch.py to default discover_models
to True, triggering /models HTTP probes on unreachable endpoints.
With 4 unreachable internal endpoints at ~6s timeout each, the
/api/model/options endpoint took ~24s instead of <1s.
2026-05-16 23:05:27 -07:00
Sylw3ster
8d4766afca fix(api_server): coerce stringified booleans in request payloads 2026-05-16 23:02:02 -07:00
teknium1
47823790b0
refactor(run_agent): review fixes — keyword-forward __init__, drop dead code, tighten guards
Four fixes from PR #27248 review:

1. **__init__ forwarder is now keyword-forwarded** (daimon-nous review).
   Previously the run_agent.AIAgent.__init__ wrapper forwarded all 64
   params positionally to agent.agent_init.init_agent, so adding a
   65th param on main would require three lockstep edits (signature,
   init_agent signature, forwarder call) or silently shift every value.
   Keyword forwarding makes this trivially safe — adding a param now
   only needs the two signatures and one extra keyword line.

2. **Drop dead _ra() in agent/codex_runtime.py** (daimon-nous + Copilot).
   The lazy run_agent reference was defined but never called inside
   this module — the codex paths use agent.* accessors only.

3. **Drop unused imports in agent/codex_runtime.py** (Copilot):
   contextvars, threading, time, uuid, Optional. Carried over from
   run_agent.py during the original extraction.

4. **Tighten three source-introspection test guards** (Copilot):
   - test_memory_nudge_counter_hydration.py — was scanning the
     concatenated source of run_agent.py + agent/conversation_loop.py
     and matching self.X or agent.X form.  Now asserts the
     hydration block lives in agent/conversation_loop.py specifically
     with the agent.X form — the body never moves back, so if it
     ever drifts a future re-introduction fails the guard.
   - test_run_agent.py::TestMemoryNudgeCounterPersistence — anchor on
     agent.iteration_budget = IterationBudget exactly (was just
     iteration_budget = IterationBudget) so an unrelated identifier
     ending in iteration_budget can't match.
   - test_run_agent.py::TestMemoryProviderTurnStart — assert the
     agent._user_turn_count form directly (the extracted body uses
     agent.X, not self.X — accepting either was a transitional fudge).
   - test_jsondecodeerror_retryable.py — scan agent/conversation_loop.py
     only, not the concatenation.

Not addressed in this commit:

* Pre-existing bugs in agent/tool_executor.py (heartbeat index
  mismatch when calls are blocked, _current_tool clobber in result
  loop, blocked-counted-as-completed in spinner summary, dead
  result_preview computation). These were preserved byte-for-byte from
  the original _execute_tool_calls_concurrent — worth a separate
  follow-up PR with proper tests.
* _OpenAIProxy.__instancecheck__ concern — pre-existing, not flagged
  by any of the original test patches (nothing actually does
  isinstance(x, OpenAI) against the proxy instance).
* agent_init.py:949 mem_config potential NameError — pre-existing;
  only triggers if _agent_cfg.get('memory', {}) itself raises, which
  it can't with a stock dict.

tests/run_agent/ + tests/agent/: 4313 passed, 1 pre-existing
test_auxiliary_client failure (unchanged).

run_agent.py: 3821 -> 3937 lines (+116 from the keyword-forwarded
init call's verbosity).  Final: 16083 -> 3937 (-12146, 75% reduction).
2026-05-16 22:55:49 -07:00
teknium1
fb138d91ca fix(install.ps1): Stage-Node honest reporting + reject empty -Stage
Two protocol-correctness gaps from review:

1. Stage-Node used [void](Test-Node) which discarded Test-Node's return
   value, so the JSON frame always reported ok=true even when Node
   install fully failed.  A GUI driver consuming the manifest couldn't
   tell 'node ready' from 'node missing'.  Wire a soft-skip channel
   ($script:_StageSkippedReason) that workers can populate to surface
   'ran, but the thing it was supposed to set up is not available' as
   skipped=true with a reason in the JSON, without aborting the install
   (Node is optional -- browser tools degrade gracefully, matches
   Write-Completion's existing 'Note: Node.js could not be installed'
   behavior).  Reset before each stage so a prior reason can't leak.

2. The -Stage dispatch used 'if ($Stage)' which is falsy for empty
   string, so 'install.ps1 -Stage ""' fell through to Main and silently
   kicked off a full destructive install.  Switch to
   PSBoundParameters.ContainsKey('Stage') so an explicit empty value
   surfaces as unknown-stage exit 2 with a structured JSON frame, the
   way every other bad stage name does.
2026-05-16 22:55:12 -07:00