The same credential-resolution fallback the messaging gateway now announces also runs in
tui_gateway/server.py (_resolve_runtime_with_fallback, used_fallback=True only logged 'Primary
auth failed, falling back to ...') and in cron/scheduler.py (_resolve_job_runtime, 'fallback
resolved to ...'), with no user-visible notice on either surface.
- hermes_cli/fallback_config.pre_agent_fallback_notice: shared text builder (gateway/run.py now
calls it) so the three pre-agent paths cannot drift in wording.
- TUI/Desktop: _resolve_agent_model_runtime tags the fallback runtime; _make_agent pops the tag
and sets agent._pending_fallback_notice, emitted once via status_callback on the first reply.
- cron: _resolve_job_runtime tags the runtime; the setup pops it and run_job prepends the notice
to a deliverable final_response ([SILENT] and the [CRON_FAILURE] first-line marker keep their
contracts) — a cron agent has no status rail, so the report is the only place it can surface.
Tests drive the production entry points (_make_agent, run_job) with the resolver and AIAgent
stubbed; each goes red with its attach/prepend removed.
resolve_runtime_provider returns the bare billing class 'custom' for every
named providers:/custom_providers: entry; the configured id only survives in
requested_provider. All three fallback resolvers (gateway, TUI/desktop, cron)
persisted runtime['provider'] as the agent identity, so an automatic fallback
labeled the session 'custom' in the UI and billing rows, while a manual
/model switch to the same provider showed the configured name.
New shared helper hermes_cli.fallback_config.effective_runtime_provider()
upgrades the bare class back to the entry's configured identity (ad-hoc
provider: custom entries stay unchanged), applied at all three sites —
same class as the delegate_tool fix.
resolve_entry_api_key() and the duplicated _fallback_entry_api_key()
read key_env via a raw os.getenv(), bypassing per-profile secret
scoping in the multiplexed gateway. Under multiplexing this can hand
a fallback request another profile's credential. Both now resolve
through agent.secret_scope.get_secret(), which reads the active
profile scope when multiplexing is on and falls back to os.environ
unchanged when it's off, so single-profile behavior is preserved.
Closes#74311
A fallback chain entry can name its API key via key_env (or the
api_key_env alias) per the fallback-providers docs, but only the gateway
path resolved it — TUI/desktop, cron, and CLI setup fallbacks ignored it,
so a fallback provider whose key lives in a non-standard env var never
resolved on those surfaces.
Centralize the inline-api_key-then-key_env lookup in
hermes_cli/fallback_config.resolve_entry_api_key() and use it at all four
fallback resolution sites (tui_gateway, cron scheduler, gateway runner,
CLI setup mixin); the CLI mixin also gains the base_url passthrough the
other surfaces already had.
Salvaged from PR #43861 (surgical reapply — the original branch predates
the #65264 fallback restructuring).