fix(left-core): an auto route on a core-era copilot choice installs the plugin

A home with model.provider: auto, auth.json active_provider: copilot and an
env token resolved "auto", so recover_for_provider found no left-core
provider and returned before checking the home's stored choice. Auth then
ignored copilot (no registry row without the plugin) and the quiet CLI, which
resolves before any gateway or agent start can migrate, showed the generic
setup error instead of installing the plugin.

"auto" (or no name) now stands for auth.json's active_provider, the provider
the resolver's auto rung follows.
This commit is contained in:
Teknium committed 2026-10-26 01:28:50 -07:00
1 parent 0e38973807
commit cf628b6788
2 files changed
+22 -4

No files matched your search

+7 -4
View File
@@ -606,12 +606,15 @@ def recover_for_provider(name: Optional[str]) -> None:
(whose start runs the migration) exists, so they would otherwise stop at "unknown provider" on a home no
``hermes update`` migrated. Outcomes print in a declared terminal front end (:func:`serve_notices_on_stderr`),
else wait for the home's next turn someone sees."""
feature = _provider_feature(name or "")
if feature is None or _installs_held.get(): # held: a read-only diagnostic consumes no kept notice either
return
from hermes_constants import get_hermes_home, hermes_home_key
from providers import get_provider_profile
home = Path(get_hermes_home())
name = str(name or "").strip().lower()
if name in ("", "auto"): # the auto rung follows auth.json's active_provider (a core-era choice)
name = str(_auth_store(home).get("active_provider") or "").strip().lower()
feature = _provider_feature(name)
if feature is None or _installs_held.get(): # held: a read-only diagnostic consumes no kept notice either
return
# A terminal front end shows outcomes now, also those another process (the gateway this CLI started) kept;
# otherwise (Desktop, gateway, a detached turn) the home's next watched turn in any process gets them. Never
# inferred from a TTY: a foreground `hermes gateway run` has one, and its resolutions serve the chat user.
@@ -621,7 +624,7 @@ def recover_for_provider(name: Optional[str]) -> None:
say(message)
if hermes_home_key(home) in _attempted: # this process already tried: skip the config/auth/.env reads
return
if plugin_present(feature.plugin, home) or get_provider_profile(str(name).strip().lower()) is not None:
if plugin_present(feature.plugin, home) or get_provider_profile(name) is not None:
return
if feature_in_use(feature, home, process_env=True):
recover_at_startup(say=say)
@@ -90,6 +90,21 @@ def test_a_failed_attempt_is_not_rechecked_on_every_resolution(tmp_path, monkeyp
def test_an_auto_route_following_the_stored_active_provider_installs_it(tmp_path, monkeypatch, installs):
"""``provider: auto`` follows auth.json's ``active_provider``: a core-era copilot choice is use."""
from hermes_cli.auth import AuthError
from hermes_cli.runtime_provider import resolve_runtime_provider
home = _copilot_home(tmp_path, monkeypatch, "model:\n provider: auto\n")
(home / "auth.json").write_text('{"active_provider": "copilot"}', encoding="utf-8")
monkeypatch.setenv("COPILOT_GITHUB_TOKEN", "gho_fixture")
try:
resolve_runtime_provider()
except AuthError:
pass # the stand-in install failed: what matters is that it was attempted
assert installs == [("github-copilot", True)]
@pytest.mark.parametrize("config", [
"model:\n provider: nous\ncron:\n model_provider: copilot\n",
"model:\n provider: nous\ndelegation:\n backends:\n model_map:\n options:\n"