Commit graph

801 commits

Author SHA1 Message Date
webtecnica
398cf40c08 fix(nous): restore inference-api.nousresearch.com base_url
The upstream migration to inference.nousresearch.com broke routing for
inference-api.nousresearch.com. Restore the correct base_url and drop
the stale alias match in _is_nous_inference_route().

Fixes #60715
2026-07-14 21:31:04 -07:00
HexLab98
f5f79ff1b4 fix(telegram): instrument getUpdates request via subclass re-tag, not slotted attr
PTB's HTTPXRequest/BaseRequest use __slots__. On Python 3.13 their
instances no longer carry a __dict__, so the getUpdates progress
instrumentation's `request.do_request = wrapper` monkey-patch raises
`AttributeError: 'HTTPXRequest' object attribute 'do_request' is
read-only`, failing every Telegram connect (#64482). It only worked on
Python 3.12, where the instance still had a __dict__ — the Docker image
ships Python 3.13, so the release broke Telegram outright.

Re-tag the request to a thin `__slots__ = ()` subclass that overrides
do_request instead of mutating the instance. This preserves the exact
progress-observation semantics, works on both 3.12 and 3.13, and covers
the real request and the test doubles alike.
2026-07-14 21:25:44 -07:00
Ben Barclay
9884b4faad
fix(cron/chronos): cache PyJWKClient across fires to stop JWKS fetch storm (#64641)
The inbound cron-fire verifier constructed a fresh PyJWKClient on every
fire, discarding the client's key cache and forcing a synchronous JWKS
HTTP GET to the portal on each fire. Under a burst of concurrent fires
(a hosted instance with several cron jobs firing in the same window) this
fanned out into N simultaneous JWKS fetches that the portal rate-limited
(HTTP 403 -> verification fails -> agent 401), or that blocked the event
loop long enough that the fire webhook could not return its 202 before
the relay's 30s timeout (observed in prod as relay 504s concentrated on
high-job-count instances).

Cache one PyJWKClient per JWKS URL at module scope (double-checked lock)
so the signing keys are reused across fires; NAS keys rotate rarely, so
the steady state is zero JWKS fetches per fire.

Regression test proves 5 fires -> 1 client construction (was 5).
2026-07-15 09:27:35 +10:00
Teknium
1f216de3a8 fix(slack): gate feedback buttons behind rich_blocks as documented
The docs state feedback_buttons requires rich_blocks: true, but
_maybe_blocks rendered full Block Kit whenever feedback_buttons alone
was enabled — implicitly turning on rich-block rendering the user never
opted into. Align the code with the documented contract and add a
regression test.
2026-07-14 13:58:36 -07:00
kshitijk4poor
fc8f8ad33f fix(slack): clear uniquely scoped assistant status 2026-07-14 13:58:36 -07:00
kshitijk4poor
38cfae9b54 fix(slack): scope Agent View workspace state 2026-07-14 13:58:36 -07:00
kshitijk4poor
4554fe128a fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
Edison42
f1328a6bfd feat(slack): cover agent view assistant APIs 2026-07-14 13:58:36 -07:00
Edison42
9a3b676fed feat(slack): support agent view manifests 2026-07-14 13:58:36 -07:00
kshitijk4poor
1f41bdbecd fix(upstage): collapse unknown future efforts to high; behavior-contract tests
Review findings from the 4-angle pass:
- Unknown-but-enabled effort levels now collapse to Solar's strongest
  (high) instead of silently downgrading to the medium default — guards
  against the next #62650-style vocabulary addition. Explicit-empty
  effort keeps the medium default.
- fallback_models test now asserts the behavior contract (non-empty, no
  denied families) instead of freezing the exact model tuple
  (change-detector, AGENTS.md reject reason).
- Drop unused pytest import in test_upstage_provider.py.
2026-07-15 00:09:24 +05:30
kshitijk4poor
f88cac71bc fix(upstage): map 'ultra' reasoning effort to Solar's high
Main added max/ultra effort levels (#62650) after this PR branched;
without the mapping 'ultra' silently fell through to the medium default.
Matches the xhigh/max collapse-to-strongest convention used by other
profiles.
2026-07-15 00:09:24 +05:30
Changhyun Min
35d3fc3b09 refactor(agent): drop the solar-pro rolling alias, default to solar-pro3
Pin the Upstage default to the concrete solar-pro3 instead of the
solar-pro rolling alias:
- plugin fallback_models is now ("solar-pro3",); entry [0] is the setup default
- drop the "solar-pro" context-window fallback entry (solar-pro3 covers it)
- update the reasoning default-on docstring and profile tests accordingly

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:09:24 +05:30
Changhyun Min
0031c5c371 refactor(agent): treat unknown Solar models as reasoning-capable
Invert the reasoning-support check from an allow-list (solar-pro,
solar-open) to a deny-list of the known non-reasoning families
(solar-mini, syn-pro). Newly released Solar models now get
reasoning_effort by default instead of having it silently dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:09:24 +05:30
Changhyun Min
20502b407c feat(agent): add Upstage Solar as a model provider
Adds Upstage Solar as a bundled model-provider plugin. Solar exposes an
OpenAI-compatible chat-completions endpoint at https://api.upstage.ai/v1, so
the generic chat_completions transport handles request/response/streaming/tool
calls — the profile is the core integration.

Provider registration (Upstage isn't in models.dev, so each registry that does
not auto-wire from the plugin layer needs an explicit entry — same pattern as
nvidia/gmi):
- plugins/model-providers/upstage/: UpstageProfile + plugin.yaml. Picker default
  and offline catalog list only the agentic Solar Pro models, led by `solar-pro`
  (rolling alias for the latest Pro). default_aux_model empty so aux tasks use
  the main model. `solar` alias. UPSTAGE_BASE_URL overrides the host.
- hermes_cli/providers.py: HERMES_OVERLAYS + label + `solar` alias, so
  resolve_provider_full('upstage') resolves (without this, an explicit
  `provider: upstage` in config was dropped and fell through to auto-detect).
- hermes_cli/auth.py: PROVIDER_REGISTRY entry + `solar` alias, so `hermes
  doctor` / resolve_provider recognise upstage (the static-registry path the
  lazy profile-extension doesn't reliably cover at validation time).
- hermes_cli/models.py: CANONICAL_PROVIDERS entry places Upstage Solar in the
  curated picker order (above the auto-appended `custom`).
- agent/model_metadata.py: context-window fallbacks (/v1/models omits
  context_length); `solar-pro` carries the 128K Pro context as the catch-all.

Reasoning: UpstageProfile.build_api_kwargs_extras wires Solar's top-level
`reasoning_effort` (low|medium|high; xhigh/max→high). Reasoning-capable families
are solar-pro* and solar-open*; solar-mini/syn-pro never receive it. Defaults ON
at medium when unset (matches the /reasoning "medium (default)" label);
`/reasoning none` disables; explicit/saved settings are honored. No
reasoning_content echo handling needed (unlike DeepSeek/Kimi).

Web dashboard:
- web/src/pages/EnvPage.tsx: add an "Upstage Solar" provider group so
  UPSTAGE_API_KEY / UPSTAGE_BASE_URL appear under LLM Providers (not "Other").

Docs/tests:
- .env.example: documents UPSTAGE_API_KEY / UPSTAGE_BASE_URL.
- tests: profile wiring, reasoning_effort mapping (pro/open/mini, efforts,
  disabled, default-on), provider-resolver regression (resolve_provider_full /
  get_provider / solar alias / overlay), `solar-pro` default.

Testing: pytest tests/providers tests/plugins/model_providers
tests/hermes_cli/test_upstage_provider.py tests/run_agent/test_provider_parity.py
tests/hermes_cli/test_api_key_providers.py; ruff clean. Verified end-to-end:
`hermes doctor` shows "Upstage Solar", and live chat works via both
`--provider upstage` and `--provider solar`. Reasoning wire format per
https://console.upstage.ai/api/docs/for-agents/raw. Platforms tested: macOS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:09:24 +05:30
Jeffrey Quesnelle
77d5b2d573
Merge pull request #59846 from bbednarski9/bbednarski/nemo-relay-upgrade
feat(nemo-relay): nemo-relay observability version upgrade to support dynamic plugin activation
2026-07-14 13:15:49 -04:00
Teknium
4e6e5181c6 refactor(telegram): drop dead _content_is_pipe_table_primary helper
After #53825's fix removed the auto-rich table bypass from
_rich_delivery_enabled(), _content_is_pipe_table_primary() had zero
callers. Remove it and simplify _rich_delivery_enabled() to the bare
rich_messages opt-in check (content param no longer used).
2026-07-14 06:48:53 -07:00
liuhao1024
34d07732dd fix(telegram): respect rich_messages config for pipe table routing
Remove the pipe-table bypass from _rich_delivery_enabled() so that
rich_messages: false is fully honoured.  Previously, pipe tables were
auto-routed to sendRichMessage regardless of the config flag, breaking
delivery on clients without Bot API 10.1 support (AyuGram, Telegram
Web, some desktop clients).

Fixes #53824
2026-07-14 06:48:53 -07:00
Roseyco-management
b8295cf6f7 fix(telegram): gate polling health on getUpdates progress 2026-07-14 17:32:41 +05:30
Roseyco-management
202be02ac9 test(telegram): define polling progress contract 2026-07-14 17:32:41 +05:30
Jaret Bottoms
e16743b0d5 fix(telegram): diagnose blocked-loop init hangs, unbind DoH from system DNS
The #63309 hang class — gateway stuck at 'Connecting to Telegram
(attempt 1/8)' with no retry, no timeout, for minutes — can only occur
when the event loop thread itself is blocked in a synchronous call:
_await_with_thread_deadline's timer fires off-loop, but its expiry
hand-off (call_soon_threadsafe) still needs the loop to run, and the
gateway's outer wait_for is a pure loop timer. When the loop is pinned,
every layer goes silent simultaneously and the process wedges with no
evidence of where.

Two changes:

1. Loop-blocked watchdog in _await_with_thread_deadline: a second
   daemon timer fires one grace period (5s) after the deadline; if the
   loop still hasn't processed the expiry, it logs a WARNING from the
   timer thread and faulthandler-dumps all thread stacks to stderr —
   converting the silent hang into a trace that names the exact
   blocking frame. A threading.Event set by the expiry callback (and on
   normal exit) keeps completed awaits from ever being misreported.

2. discover_fallback_ips: the system-resolver leg runs
   socket.getaddrinfo in a worker thread with no timeout, and
   asyncio.gather waited on it unboundedly — a wedged OS resolver
   stalled discovery for minutes between the two startup log lines. Its
   result only feeds a log message, so it no longer gates discovery:
   DoH legs (already client-bounded) are gathered alone and the system
   leg is awaited with a _DOH_TIMEOUT cap, best-effort.

Refs #63309

Tests: 3 watchdog regressions (blocked-loop dump fires; responsive-loop
timeout does not; completed await does not) + 2 hung-resolver
regressions (DoH results returned promptly; worst-case seed fallback
stays bounded).
2026-07-14 17:09:50 +05:30
SilentKnight87
2a0dd95ccf fix(telegram): classify and dedup post-reconnect probe failures (#63243) 2026-07-14 17:08:36 +05:30
Teknium
861d69c7bb
fix(dashboard): keep memory.provider in the config schema so Desktop's dropdown survives (#63886)
The dashboard's dedicated memory-provider UI (4b184cbe5) excluded
memory.provider from /api/config/schema server-side. Desktop's settings
page builds its field list from that schema, so the Memory Provider
dropdown silently vanished from Desktop after v0.18.1.

- web_server.py: restore memory.provider as a select in _SCHEMA_OVERRIDES,
  with options built from plugins.memory discovery (was a stale hardcoded
  [builtin, honcho] list before the removal)
- plugins/memory: add list_memory_provider_names() — directory-scan-only
  name listing, safe at module import time (no provider imports)
- web ConfigPage: hide memory.provider client-side instead — the Plugins
  page owns the dedicated provider-switching UI there
- tests: schema contract (select present, category memory, builtin
  sentinel) + invariant that every discoverable provider is selectable
2026-07-13 18:56:51 -07:00
Bryan Bednarski
7e201fa1b6
fix(nemo-relay): align dynamic plugin configuration
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
2026-07-13 17:01:08 -06:00
kshitijk4poor
2fc3f9c1ff fix(deepinfra): harden multimodal provider routing
Prevent credential forwarding across catalog redirects, retain explicit opt-in semantics for paid media backends, fail closed on invalid provider configuration, avoid mixed-catalog and output-limit assumptions, and reserve native STT provider names.
2026-07-14 02:59:39 +05:30
Georgi Atsev
fe002eb124 feat(providers): Support DeepInfra as an LLM provider 2026-07-14 02:59:39 +05:30
Bryan Bednarski
2d2bed5891
Merge origin/main into bbednarski/nemo-relay-upgrade
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
2026-07-13 14:50:20 -06:00
Teknium
d48bf743f2 fix(approval): scope smart deny owner overrides to one operation
Co-authored-by: Sergei Ivanov <kavi@local.hermes>
2026-07-13 04:31:55 -07:00
Teknium
98b4562947 fix(kanban): make Done-card results actionable 2026-07-13 01:49:35 -07:00
iborazzi
deae8e3b4d feat(kanban): surface final_result for Done cards; show run summary when task.result is empty 2026-07-13 01:49:35 -07:00
Teknium
a10081f83b test(image-gen): cover Codex capability HTTP boundary 2026-07-12 23:43:49 -07:00
Teknium
402969670d fix(image-gen): classify unsupported Codex image accounts 2026-07-12 23:43:49 -07:00
Teknium
8030b01a2a fix(kanban): harden durable artifact handoff 2026-07-12 23:09:41 -07:00
Teknium
aaf5691261
feat(kanban): collect project directory when creating boards (#63249) 2026-07-12 17:51:30 -07:00
Teknium
f67aae3230
fix(kanban): make scratch cleanup explicit in dashboard (#63123) 2026-07-12 04:15:59 -07:00
Teknium
7550c594ce
feat(reasoning): add max and ultra effort levels (#62650) 2026-07-12 00:26:49 -07:00
teknium1
31152ae108 fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
Alex Jestin Taylor
c97d9a4c07 feat(providers): add Fireworks AI as preferred provider
Bundle Fireworks AI as a first-class BYOK provider across the CLI, web/TUI,
and desktop onboarding.

- New model-provider plugin with attribution headers (HTTP-Referer / X-Title)
  so Fireworks can attribute Hermes traffic; PAYG-safe default aux + fallback
  models (accounts/fireworks/models/...), IDs tracking fw-ai/fireconnect.
- Registered in CANONICAL_PROVIDERS so it appears in the CLI/web/TUI pickers.
- Alias wiring (fireworks-ai, fw) into both CLI resolvers.
- First-class wiring: OPTIONAL_ENV_VARS, HERMES_OVERLAYS (FIREWORKS_BASE_URL
  override), doctor env hints. Live catalog + model_metadata are auto-derived.
- doctor: treat Fireworks' native slash-form IDs (accounts/fireworks/...) as
  valid, not aggregator vendor prefixes, so it no longer tells Fireworks users
  to switch to openrouter or drop the prefix.
- picker: plugin providers with no static curated list now lead with their
  profile fallback_models, so the default is an agentic chat model instead of
  whatever the live catalog returns first (Fireworks listed an image model,
  flux-*, ahead of its chat models).
- Desktop onboarding: Fireworks as a RECOMMENDED hero card with the official
  Fireworks logomark and a brand-purple badge, routing to the BYOK key form;
  i18n in en/ja/zh/zh-hant.
- Tests: profile contract, first-class wiring (both resolvers, overlay, config,
  doctor incl. the slash-form regression, aux headers, credentials), discovery
  spot-check, and a live smoke test driven through the Hermes runtime.

Fire Pass (fpk_) support is coming soon; the future wiring is kept as a
commented-out scaffold in the plugin.
2026-07-11 05:43:35 -07:00
kshitijk4poor
cf34a1e8c7 fix(security): cover remaining catalog credential paths 2026-07-11 12:28:55 +05:30
kshitijk4poor
4aa499ff9f fix(telegram): harden flood fallback recovery
Keep empty-tail recovery scoped to the current stream segment and bound fallback flood retries. Preserve Telegram's server retry hint without blocking final delivery through a long cooldown.
2026-07-11 11:13:50 +05:30
Gille
04898631cb fix(telegram): recover final delivery after stream flood 2026-07-11 11:13:50 +05:30
liuhao1024
97fb9e1f62 fix(telegram): classify PTB heartbeat transport errors 2026-07-10 19:28:03 +05:30
Gille
3aeaf3755d fix(nous): forward provider routing through Portal 2026-07-10 13:10:45 +05:30
luxuguang-leo
949e4cb72a fix(feishu): add extra_ua_tags=["channel"] to FeishuWSClient for group @mention delivery
Without this UA tag the Feishu server does not push group @mention events
over the WebSocket transport. The "channel" tag tells the server to use
the Channel protocol which enables group-message routing in addition to P2P
direct messages.

Root cause: FeishuWSClient was created without any UA signaling tag, so the
server defaulted to the basic DM-only push mode. Group @mention events were
silently dropped before reaching Hermes.

Fixes https://github.com/NousResearch/hermes-agent/issues/50656

Also adds a regression test verifying the UA tag is present in the
FeishuWSClient constructor call.
2026-07-09 20:31:49 -07:00
lemonwan
0f8603c571 test(gateway): regression: every adapter.connect() must accept is_reconnect
The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to #59429 (which fixed the original QQAdapter offender).
2026-07-09 19:09:38 -07:00
teknium1
a801046669 fix(memory): resolve() the shared-connection registry key; symlink test
Follow-ups for salvaged PR #43819: the registry key was
str(Path(db_path).expanduser()) — a symlinked or relative path to the
same DB file got its own connection, silently reintroducing the exact
multi-writer contention the registry prevents. Key on Path.resolve()
(OSError-tolerant fallback). Adds a symlink regression test and the
AUTHOR_MAP entry for adambiggs.
2026-07-09 18:17:40 -07:00
Adam Biggs
b5226caff8 fix(memory): share one SQLite connection per holographic store database
Every MemoryStore instance opened its own SQLite connection guarded by
its own RLock. Several providers coexist in one process (the main agent
plus every delegate_task subagent), so instances pointing at the same
memory_store.db raced as independent WAL writers. Combined with writes
that were not rolled back on error, one connection could leave an open
write transaction that pinned the write lock and made every other
connection's writes fail with "database is locked" for the full busy
timeout.

Instances for the same database now share ONE process-wide connection
and ONE re-entrant lock, so access is fully serialized and
cross-connection contention is impossible. The shared connection is
refcounted: closing one instance never tears it out from under a live
sibling, and the last close releases it. The connection runs in
autocommit (isolation_level=None) so a write that raises mid-method can
never leave a dangling transaction holding the write lock; the existing
explicit commit() calls become harmless no-ops.

The provider's shutdown() now calls the refcount-guarded close() instead
of just dropping the reference: leaving finalization to GC kept the
connection (and its write lock) alive indefinitely on long-running
gateways, prolonging the exact contention this fix removes. The last
provider now releases the connection deterministically while siblings
stay live; regression tests fail without the wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:17:40 -07:00
kshitijk4poor
709da844b5 feat(gateway): attach MEDIA: caption to the media bubble on standalone sends
hermes send "MEDIA:/x.png This Caption" now arrives as one native captioned
bubble instead of a separate text message followed by an uncaptioned bubble.

Root cause: the standalone senders (hermes send / cron / send_message tool)
stripped the MEDIA: tag, sent the remaining text as its own message, and
called the media send with no caption -- even though hermes send's help
advertises the captioned form and the bridges/adapters already support a
caption. Signal already captioned correctly.

- tools/send_message_tool.py: new _media_caption_split() chokepoint decides
  caption-vs-separate-body (single captionable non-voice file within the
  platform's message-length cap). Wired into the Telegram, WhatsApp and
  Discord dispatch paths.
- Telegram/WhatsApp/Discord: when the single captioned file is missing, the
  caption text is delivered as a plain message so it is never silently lost.
- Telegram caption send gets a MarkdownV2->plain parse fallback.
- Tests: _media_caption_split unit tests + per-platform caption tests
  (ride, multi-file fallback, voice exclusion, over-limit fallback,
  missing-file text fallback); updated the 3 tests that asserted the old
  text-then-media split.

Closes the gap reported against #58911 (the MEDIA_CAPTION directive PR);
credit to @ferreiraesilva for surfacing the caption behavior.
2026-07-09 15:38:32 +05:30
pedrommaiaa
724ab9098d perf: avoid broad message prep deepcopies
(cherry picked from commit 030746c560)
2026-07-09 14:35:14 +05:30
kyssta-exe
e10c8eba00 fix(whatsapp): use windows_detach_popen_kwargs to prevent console window flash on Windows 2026-07-08 07:49:22 -07:00
Shannon Sands
4f620a0bbc Add WhatsApp dashboard pairing flow 2026-07-07 17:45:51 -07:00