Commit graph

2540 commits

Author SHA1 Message Date
Teknium
5f1991bf6b
fix(gateway): import PairingStore in _start_secondary_profile_adapters (#65118)
The served-profiles block in _start_secondary_profile_adapters references
PairingStore, but the class's only import in gateway/run.py is method-local
inside __init__ — so the reference raised NameError at runtime, silently
swallowed by the enclosing try/except ('could not record served_profiles').
Result: multiplexing gateways never created per-profile pairing stores, and
authz pairing checks for secondary profiles fell through to the global
whitelist. Also masked the served_profiles runtime-status write.

One-line fix (local import alongside write_runtime_status) + regression
tests that drive the real method and assert the stores materialize,
verified red without the import and green with it.

Surfaced during the profile-routing sweep by @CocaKova's PR #61689, which
included the same fix as part of a larger feature.
2026-07-15 12:11:16 -07:00
Jason
c346f018d4 fix(gateway): evict stale-self-heal agent cache entries pointing at dead sessions
The #54878 self-heal (SessionStore.get_or_create_session) drops a routing
key pointing at a session already ended in state.db and recovers/recreates
a fresh session_id under the same session_key. The #54947 fix (agent-cache
cache-hit guard in gateway/run.py) treats a cached agent whose snapshot
session_id differs from the current session_id, under the same
session_key, as an intentional /resume-/branch-style switch between two
live sibling conversations, and reuses it unchanged to protect the prompt
cache.

These two fixes compose incorrectly: when the #54878 self-heal just fired,
the cached agent's session_id is not a live sibling — it's the dead session
just routed away from. #54947's "different session_id -> reuse freely" rule
reuses it anyway. The stale agent runs the turn, and the post-run "session
split" sync (agent.session_id != session_id) then writes the routing key
straight back onto the dead session_id, undoing the self-heal. This repeats
on every subsequent message until an interrupt (e.g. /stop) happens to race
in before that post-run sync, silently discarding conversation context.

Reproduced live on the engineering gateway (2026-07-12, routing key
agent:main:telegram:dm:170829464:544520): 5 consecutive self-heal log lines
over ~40 minutes, each followed by the dead session_id being reused and
re-synced back, until an interrupted /stop finally let a fresh session
stick — at which point all prior context was gone.

No open upstream issue tracks this specific interaction as of 2026-07-12
(checked #54878, #54947, #59580, #59597, #61220 — all cover adjacent but
distinct edges of the self-heal / agent-cache system).

Fix: before applying #54947's reuse-on-mismatch rule, check (outside the
cache lock, via SessionStore._is_session_ended_in_db) whether the cached
snapshot's session_id is itself ended in state.db. If so, treat it as a
stale self-heal artifact and evict/rebuild fresh -- same as a genuine
cross-process write -- instead of reusing it. Re-validates the peeked
verdict against the tuple actually held under the lock so a race can't
apply a stale verdict to a different (possibly live) cache entry.

Tests: tests/gateway/test_stale_self_heal_agent_cache_eviction.py (5 new
cases: dead-session eviction, live-sibling reuse preserved [#54947 intact],
cross-process invalidation preserved [#45966 intact], same-session_id dead
edge case, lock-race re-validation). Full tests/gateway/ suite: 14 failed,
9040 passed, 11 skipped -- all 14 failures verified pre-existing on
unpatched main (confirmed via git stash + re-run), unrelated to this
change.
2026-07-16 00:11:22 +05:30
Frowtek
170959d80a fix(gateway): sanitize sender-name prefix in shared multi-user sessions
GatewayRunner._prepare_inbound_message_text() interpolated
source.user_name — the platform-supplied, user-settable display name —
directly into the message text of every turn in a shared multi-user
session: f"[{source.user_name}] {message_text}". Shared sessions are the
default for any threaded conversation (thread_sessions_per_user defaults
to False) and apply to any group with group_sessions_per_user=False, so
no special configuration is needed to reach this path.

An unescaped display name containing embedded newlines could therefore
masquerade as a new markdown section (a fake "## Override" heading)
inside the live conversation the model reads on every turn — the same
indirect-prompt-injection vector gateway/session.py's
build_session_context_prompt() already guards against for the identical
user_name field via _format_untrusted_prompt_value(), which was never
applied to this sibling call site.

Add neutralize_untrusted_inline_text() alongside the existing helper in
gateway/session.py: it collapses embedded newlines/control characters to
a single inert line without JSON-quoting, so inline "[Name] message"
formatting is preserved byte-for-byte for the common case (unlike
reusing _format_untrusted_prompt_value directly, which would add visible
quote marks to every sender prefix). Wire it into the sender-prefix
construction in gateway/run.py.
2026-07-15 23:48:34 +05:30
Teknium
647520f83e fix(gateway): gate profile routing on multiplex_profiles + widen batch-key routing to all adapters
Follow-ups on the salvaged #20096 profile-routing feature:

- _profile_name_for_source now returns None unless gateway.multiplex_profiles
  is on. Routing stamps source.profile, which namespaces session/batch keys,
  but the profile-scoped agent run only activates under multiplexing — without
  the gate, configured routes with multiplexing off split batch/session keys
  into agent:<profile> while the agent still ran from agent:main.
- Widen the profile-aware _text_batch_key fix from Discord to every adapter
  that builds batch keys via build_session_key (telegram, whatsapp, matrix,
  feishu, wecom, weixin) — routing is platform-generic, so the batch-key
  namespace fix must be too.
- Downgrade the no-route-matched log from INFO to DEBUG (fired on every
  unrouted inbound message).
- GatewayConfig.to_dict(): serialize profile_routes as plain dicts
  (ProfileRoute dataclasses are not JSON-safe).
- Docs: correct the 'independent of multiplexing' claim in
  docs/profile-routing.md (routing requires multiplexing), fix the
  platform-only specificity row (0, not 1), and document profile_routes in
  website/docs/user-guide/multi-profile-gateways.md.
- Tests: pin the multiplex gate (routes ignored when off, active when on,
  build_source end-to-end stays in agent:main when off).
2026-07-15 09:50:05 -07:00
Burgunthy
f58e4622cf fix(gateway): profile routing — conjunctive matching + universal gateway_runner
Addresses hermes-sweeper review on #20096.

Problem 1 (profile_routing.py): route matching returned True on a chat_id
hit before the guild_id constraint was consulted, so a route declaring both
guild_id and chat_id matched on chat_id alone. Restored conjunctive (AND)
semantics — every declared discriminator must hold; hierarchical parent_chat_id
matching is preserved. Added a regression test for the guild+chat case.

Problem 2 (base.py / run.py): gateway_runner was injected only when an adapter
pre-declared the attribute, and only Discord did — so build_source never called
_profile_name_for_source for Telegram/Feishu/Slack/etc., despite the
platform-generic claim. Declared gateway_runner on BasePlatformAdapter and made
the plugin-registry injection unconditional, so profile routing now reaches
every platform. Added non-Discord (Telegram) resolution coverage and an
injection-inheritance test.

Also adds docs/profile-routing.md documenting gateway.profile_routes
(matching rules, specificity, profile isolation) — requested in review.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Burgunthy
e8b7ce8c19 fix(session): persist profile_name and route batch key by profile
Two follow-ups observed after deploying profile routing:

1. sessions.profile_name was NULL even when the agent ran inside the
   routed profile scope. _insert_session_row never wrote it,
   get_or_create_session / reset_session never passed it through, and
   the agent-side _ensure_db_session fallback had no way to read it.
   - Declare profile_name TEXT in SCHEMA_SQL so _reconcile_columns
     auto-adds it on existing DBs.
   - _insert_session_row takes profile_name and writes it.
   - SessionStore passes source.profile (or old_entry.origin.profile
     on reset) into db_create_kwargs.
   - _ensure_db_session reads the active profile via
     get_active_profile_name() inside _profile_runtime_scope.

2. DiscordAdapter._text_batch_key called build_session_key without
   profile=, so the batch key always landed in agent:main even when
   the routed profile differed — diverging from the agent session
   key namespace (agent:crypto-trader, agent:ai-expert, ...).
   Pass event.source.profile through so both namespaces agree.

Live verification (jth-server-2, 2026-06-28): a test message in a
routed #coin thread produced agent:crypto-trader:discord🧵...
in the batch log and profile_name=crypto-trader in the sessions
row. Default-routed chat still produced agent:main / NULL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Burgunthy
a1d6654264 fix(gateway): read adapter token from config for fingerprint check
_adapter_credential_fingerprint only looked at adapter.token directly,
but Discord (and similar) adapters store the bot token on their config
sub-object, not on self. Every Discord adapter in a multiplexed
gateway therefore returned None, the same-token conflict check was
silently skipped, and N adapters all polled the same bot token —
producing a per-message race where whichever adapter won the GIL
answered the user.

Adds a config-token fallback (token, then bot_token) so the check
actually fires for config-backed adapters. Direct adapter.token
still takes precedence when both exist.

Tests cover: config-backed token produces a fingerprint, distinct
tokens produce distinct fingerprints, direct token wins over config,
config without token attributes returns None.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Burgunthy
a55523fd6d refactor(profiles): drop dead helpers from hermes_constants
STANDARD_PROFILES, normalize_profile, validate_profile_name, and
is_standard_profile in hermes_constants were superseded by
hermes_cli.profiles.{normalize_profile_name, validate_profile_name}
but never removed. profile_routing.py is updated to import from the
canonical location; the old helpers are deleted.

Lazy import inside parse_profile_routes avoids the circular dependency
at module load time (hermes_constants -> hermes_cli -> hermes_constants).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Burgunthy
9166d727b8 fix(profile-routing): remove dead forum cache, warn on missing profile
Three follow-ups to the initial routing PR after code review:

1. Remove dead forum-post hierarchy cache. The `_forum_post_cache`,
   `register_forum_post()`, and `resolve_forum_channel()` were never
   wired up — no caller in the codebase. Discord's adapter already
   sets `parent_chat_id` to the immediate parent (forum channel for a
   forum post), so the existing `self.chat_id == parent_chat_id`
   branch in `matches()` handles forum posts correctly without a
   cache. The hierarchical-resolution branch in `matches()` and the
   bounded-LRU infrastructure are removed.

2. Fix docstring specificity numbers (8 → 14, 4 → 6) and rewrite the
   "Hierarchical matching" section to describe the actual one-level
   parent_chat_id behavior. Removed unused `OrderedDict` and `Set`
   imports.

3. Warn loudly when a routed profile doesn't exist on disk. Previously,
   a typo in `profile_routes` (e.g. `crypto-tradr`) silently fell back
   to the global HERMES_HOME, causing the message to read the default
   profile's memory/credentials with no signal to the operator. Now
   emits a `logger.warning` with the profile name, source identifier,
   and the fallback reason. Bare-exception path also gets `exc_info`.

Tests:
- test_profile_routing.py: +2 tests verifying forum post matching via
  direct parent_chat_id (covers the case the removed cache was meant
  for). 31 total, all pass.
- test_profile_resolution.py: NEW, 12 tests covering resolution order
  (source.profile > routing > active > default), missing-profile
  warning, exception handling, and routing consultation. All pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Burgunthy
5e65f6d79f feat(gateway): add profile-based routing for inbound messages
Adds gateway.profile_routes config that routes specific Discord
guilds/channels/threads (and other platforms) to different profiles.
The routing engine uses hierarchical specificity matching
(thread > channel > guild) with bounded LRU caching for forum post
resolution.

Routing result is stamped on source.profile by BasePlatformAdapter
.build_source() at inbound time. When gateway.multiplex_profiles is on,
the existing _profile_runtime_scope machinery picks up source.profile
and runs the whole turn inside the profile's HERMES_HOME — so memory,
skills, config, and secrets all resolve to that profile automatically.
No new isolation code is added; this PR only adds the routing decision
layer on top of the existing multiplexing infrastructure.

Configuration:

    gateway:
      multiplex_profiles: true
      profile_routes:
        - name: server-default
          platform: discord
          guild_id: "GUILD_ID"
          profile: server-profile
        - name: special-channel
          platform: discord
          guild_id: "GUILD_ID"
          chat_id: "CHANNEL_ID"
          profile: channel-profile

When multiplex_profiles is off, profile_routes is ignored (no behavior
change for single-profile gateways).

Tests: 29 unit tests covering specificity scoring, hierarchical
matching, path-traversal validation, and config parsing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 09:50:05 -07:00
Teknium
09b6d22dfe fix(gateway): harden hidden-incomplete detection against the sentinel final_response
Follow-up to the salvaged #51657: the conversation loop returns the
retry-exhaustion sentinel as BOTH final_response and error, so the
original detector (which required final_response to be falsy) never
fired on real exhaustion turns — the sentinel text was delivered
verbatim into the channel, exactly the #51628 poisoning vector. Detect
the sentinel echo, blank it before empty-response normalization, and
never suppress a turn whose final_response is genuine model text.
Also: dedupe-guard mock fix in the test fixture (has_platform_message_id
must return False, not a truthy MagicMock) and two guard tests
(real answer never suppressed; interrupted/failed never classified).
2026-07-15 09:48:24 -07:00
LeonSGP43
08015c3a8f fix(gateway): suppress hidden-only incomplete codex turns 2026-07-15 09:48:24 -07:00
LeonSGP43
dbd8704673 fix(slack): clear stuck assistant status on /stop and via explicit metadata
Salvaged from #32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in #63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving #63709's cross-workspace safety guarantee.
2026-07-14 21:31:32 -07:00
KCAYAAI
e9d564c09c fix(gateway): resume typing after clarify reply 2026-07-14 21:31:32 -07:00
Ben Barclay
bb5fc723b6
feat(relay): consume channel context from the connector (#64649)
Phase 3 of relay-channel-context (gateway/agent side, single PR). The
connector (gateway-gateway #122/#123/#124) now attaches read-only
surrounding channel/group context to an addressed relay turn; this wires
the gateway to consume it.

- descriptor.py: additive optional supports_context (default False) on
  CapabilityDescriptor. from_json already filters unknown keys, so this is
  back-compat both directions within contract_version 1.
- ws_transport.py: _event_from_wire maps the connector's read-only
  context[] array into the EXISTING MessageEvent.channel_context field via
  a new _render_relay_context() helper — reusing the same read-only
  injection path history-backfill uses (run.py prepends channel_context
  ahead of the trigger message). Never raises; absent/empty/malformed ->
  channel_context unset (byte-identical to today).
- docs/relay-connector-contract.md: document supports_context in the §2
  descriptor table (fixes the contract-doc conformance test) + the
  context/context_error inbound fields in §3.
- tests: descriptor default/round-trip/forward-compat; _render_relay_context
  rendering + malformed-safe; _event_from_wire context->channel_context
  mapping + the read-only invariant (trigger text untouched).

RELAY-ONLY: only gateway/relay/* + the shared MessageEvent consumption via
its existing channel_context field. No native adapter touched.
2026-07-15 09:31:34 +10:00
kshitijk4poor
4554fe128a fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
Teknium
e81d18dfb4 refactor(reasoning): unify per-model reasoning resolution behind a single chokepoint
Collapse the six per-surface copies of override-then-global resolution
(CLI startup, gateway, TUI, cron, /model switch, fallback activation)
onto one shared resolve_reasoning_config() in hermes_constants.

Also fixes the gateway resolving reasoning against config model.default
instead of the session's effective model: after a session-only /model
switch, the switched model's override now applies (gateway message paths
pass the resolved session model through _resolve_session_reasoning_config;
/reasoning status reads the session model override).

Cleanup: drop docs/PER_MODEL_REASONING.md (duplicates the website docs
page), drop the change-detector _config_version test (no bump needed —
deep-merge handles new keys), remove a stale plan-reference comment.

Adds chokepoint contract tests (13) and gateway session-effective-model
regression tests (2).
2026-07-14 11:46:40 -07:00
ScotterMonk
d9cdb81923 feat(config): support per-model reasoning_effort overrides
Add agent.reasoning_overrides dict to config.yaml. Users can now set
a reasoning_effort per model, overriding the global agent.reasoning_effort.

Example:
  agent:
    reasoning_effort: "medium"       # global default
    reasoning_overrides:
      "openrouter/anthropic/claude-opus-4.5": "xhigh"
      "openai/gpt-5": "low"
      "claude-sonnet-4.6": "high"    # bare model name also works

The helper is spelling-tolerant: override keys match regardless of
provider prefix or dots-vs-dashes normalization, so users can write
keys in any sensible form and they'll match.

Resolution priority:
1. Session-scoped /reasoning --session override (gateway only; unchanged)
2. Per-model override from agent.reasoning_overrides (spelling-tolerant)
3. Global agent.reasoning_effort (existing)
4. Provider default (unchanged)

Wired into:
- CLI startup (cli.py)
- Messaging gateway agent construction (gateway/run.py)
- Desktop/TUI _load_reasoning_config (tui_gateway/server.py)
- Cron job scheduler (cron/scheduler.py)
- /model mid-session switch (agent/agent_runtime_helpers.py)
  + _primary_runtime now tracks reasoning_config for correct fallback recovery
- Fallback activation (agent/chat_completion_helpers.py::try_activate_fallback)
  + Re-resolves reasoning_config for the fallback model (best-effort)

Closes #21256 (per-model reasoning_effort defaults).

Note: no hermes config set agent.reasoning_overrides.<model> support;
users edit the YAML directly. _set_nested splits on "." and would
corrupt model keys containing version dots.
2026-07-14 11:46:40 -07:00
Rory Ford
ca559a7852 fix(gateway): never prune sessions when active-process check fails
prune_old_entries' active-process guard failed open: when
has_active_processes_fn raised, the except block logged at debug and
fell through to the age check, so sessions with live background
processes attached could still be pruned — violating the documented
invariant that such sessions are never dropped. Add a continue so an
exception in the safety check fails safe (the entry is kept).

Commit 6b408e131 fixed the session_key/session_id mismatch in this
same guard but left the exception path failing open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:13:20 +05:30
HexLab98
ca907480ae fix(gateway): allow ws_orphan_reap rows in session recovery (#63207)
Whitelist ws_orphan_reap alongside agent_close in
find_latest_gateway_session_for_peer so gateway stale-routing
self-heal can reopen wrongly-reaped messaging sessions instead of
minting empty replacements. Layer A prevention already landed in #60609.
2026-07-14 16:58:47 +05:30
dsad
fd461b58ca fix(gateway): fail closed on compression state probe errors 2026-07-14 16:56:34 +05:30
kshitij
0684506072
Merge pull request #64004 from kshitijk4poor/salvage/63274-cli-close-persist
fix(cli): persist close transcript without history alias
2026-07-14 16:45:35 +05:30
Teknium
89bd0fba90
feat(codex): redeem banked usage-limit resets via /usage reset (#64280)
OpenAI lets ChatGPT-plan Codex users bank rate-limit reset credits, but
until now they could only be redeemed from the Codex CLI/app or the
website. This wires the same backend API into Hermes:

- /usage on the openai-codex provider now shows "You have N resets
  banked - use /usage reset to activate" (parsed from the
  rate_limit_reset_credits field the /usage endpoint already returns).
- New /usage reset subcommand (CLI + gateway) redeems one banked
  credit via POST .../rate-limit-reset-credits/consume with a UUID
  idempotency key, mirroring codex-rs backend-client semantics
  (PathStyle /wham vs /api/codex, ChatGPT-Account-Id header,
  reset/nothing_to_reset/no_credit/already_redeemed outcomes).
- Guard: redemption is refused while no rate-limit window is fully
  exhausted, since a banked reset restores the FULL 5h + weekly
  allowance and spending it early wastes it. /usage reset --force
  overrides. Zero banked credits and non-codex providers are refused
  with clear messages; nothing_to_reset reports the credit was NOT
  spent.
- i18n: new gateway.usage.unknown_subcommand / reset_wrong_provider
  keys across all 16 locales; docs updated (cli.md, messaging index).

Tested with unit tests plus a real-socket E2E against a local fake
Codex backend exercising redeem/guard/force and the /usage hint.
2026-07-14 03:23:19 -07:00
kshitijk4poor
0b422559f3 fix(session): preserve clean multimodal persistence override 2026-07-14 03:32:45 +05:30
kshitijk4poor
915f1bf1bc fix(api): reserve cron fire work during drain 2026-07-13 23:26:10 +05:30
kshitijk4poor
ffc10cc659 fix(gateway): quiesce API and cron work during drains
Reserve API requests before their first await, include every drain-owned work type in runtime status, and pause local cron dispatch while a gateway drain is active.
2026-07-13 23:26:10 +05:30
kshitijk4poor
104ffeae23 fix(gateway): complete API-server shutdown drain 2026-07-13 23:26:10 +05:30
Bartok9
021ee34546 fix(gateway): drain in-flight api_server runs on shutdown
Closes #63529

Root cause: GatewayRunner._drain_active_agents only waited on
_running_agents + cron in-flight counts. Desktop/API sessions are
tracked solely inside APIServerAdapter (_inflight_agent_runs +
_active_run_agents), so stop/restart logged active_at_start=0 and
systemd SIGKILL'd mid-tool work.

Fix: APIServerAdapter.active_agent_work_count() plus
GatewayRunner._active_api_run_count() folded into the drain wait,
status updates, timeout result, and shutdown logs — same pattern as
_active_cron_job_count for #60432.

Verification: pytest tests/gateway/test_api_server_active_work_drain.py
tests/gateway/test_cron_active_work_drain.py -q → 19 passed
2026-07-13 23:26:10 +05:30
teknium1
d0e9a42cec fix(delegation): harden durable completion delivery 2026-07-13 07:28:21 -07:00
teknium1
67f4e1b4a9 feat(delegation): persist background completions 2026-07-13 07:28:21 -07:00
John Lussier
94a7705bdd fix(gateway): deduplicate completion delivery 2026-07-13 07:28:21 -07: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
SahilRakhaiya05
f96b2e6ef7 fix(whatsapp_cloud): gate interactive taps on DM allowlist 2026-07-13 16:15:04 +05:30
Teknium
8030b01a2a fix(kanban): harden durable artifact handoff 2026-07-12 23:09:41 -07:00
teknium1
837077dfae fix(api): stop producers after run transport expires 2026-07-12 04:15:47 -07:00
teknium1
8f18fa104f fix(api): separate run control from stream lifetime 2026-07-12 04:15:47 -07:00
teknium1
1da89a5f3d fix(api): keep live runs tracked past stream ttl 2026-07-12 04:15:47 -07:00
Teknium
7550c594ce
feat(reasoning): add max and ultra effort levels (#62650) 2026-07-12 00:26:49 -07:00
kshitijk4poor
4281151ae8 test(gateway): cover effective context budget 2026-07-11 23:46:13 +05:30
kshitijk4poor
265ac7d812 fix(gateway): honor runtime context budgets 2026-07-11 23:46:13 +05:30
kshitijk4poor
ff84e3f7d5 fix(gateway): scope queued context references 2026-07-11 23:46:13 +05:30
kshitijk4poor
37a942650f fix(gateway): scope context refs to runtime profile 2026-07-11 23:46:13 +05:30
TheTom
4df6e6280d fix(gateway): @ context reference expansion never ran (AttributeError)
GatewayRunner._prepare_inbound_message_text's "@" context-reference
block read self._model / self._base_url to resolve the model for
get_model_context_length_async. GatewayRunner never sets either
attribute (copy-pasted from HermesCLI in da44c196b, which does carry
self.model/self.base_url). Every message containing "@" raised
AttributeError inside the try block, silently swallowed by the
surrounding except Exception at debug level, so
preprocess_context_references_async never ran and @file:/@folder:/@diff/
etc. references passed through to the model unexpanded.

Fix: resolve model/provider/base_url via
self._resolve_session_agent_runtime(source=, session_key=,
user_config=), the same session-aware resolution the hygiene
compression block already uses a few hundred lines later in this file.

Also raise the swallow log from debug to warning (with exc_info at
debug) so a future regression here is visible instead of silent.
2026-07-11 23:46:13 +05:30
teknium1
6142203bd7 fix(gateway): ground readiness in live runtime state 2026-07-11 08:42:21 -07:00
teknium1
f9728af5e2 feat(gateway): add authenticated runtime readiness checks 2026-07-11 08:42:21 -07:00
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
kshitijk4poor
b196ce80c8 fix(gateway): unify routing save and reset races 2026-07-10 12:38:48 +05:30
kshitijk4poor
b3f77f5c82 fix(gateway): close SessionStore concurrency gaps 2026-07-10 12:38:48 +05:30
kshitijk4poor
9d38a2309e fix(gateway): enforce one async SessionStore boundary 2026-07-10 12:38:48 +05:30