fix(moa): surface stale presets without retries

Keep invalid persisted preset names fail-closed, list the valid configured choices, and classify the local lookup failure as deterministic so it reaches Desktop immediately.
This commit is contained in:
Teknium 2026-07-17 05:42:43 -07:00
parent 61bbc39330
commit 4c0546c9cc
4 changed files with 62 additions and 1 deletions

View file

@ -775,6 +775,14 @@ def classify_api_error(
if classified is not None:
return classified
# Local MoA config drift is deterministic: a persisted session can retain
# a preset name that was later renamed/deleted. Retrying the same lookup
# cannot recover and makes a clear config error look like an API outage.
from agent.errors import MoAPresetNotFoundError
if isinstance(error, MoAPresetNotFoundError):
return _result(FailoverReason.model_not_found, retryable=False)
# ── 3. Error code classification ────────────────────────────────
if error_code:

View file

@ -7,3 +7,7 @@ class EmptyStreamError(RuntimeError):
"""Raised when a provider closes a stream without yielding a response."""
pass
class MoAPresetNotFoundError(ValueError):
"""Raised when a persisted MoA preset no longer exists in config."""

View file

@ -295,7 +295,13 @@ def resolve_moa_preset(config: Any, name: str | None = None) -> dict[str, Any]:
preset_name = str(name or cfg.get("default_preset") or DEFAULT_MOA_PRESET_NAME).strip()
preset = cfg["presets"].get(preset_name)
if preset is None:
raise KeyError(preset_name)
from agent.errors import MoAPresetNotFoundError
available = ", ".join(cfg["presets"]) or "(none)"
raise MoAPresetNotFoundError(
f"MoA preset '{preset_name}' was not found. Available presets: "
f"{available}. Run `hermes moa list`."
)
return deepcopy(preset)

View file

@ -1,3 +1,6 @@
import pytest
from agent.errors import MoAPresetNotFoundError
from hermes_cli.moa_config import (
DEFAULT_MOA_AGGREGATOR,
DEFAULT_MOA_PRESET_NAME,
@ -205,6 +208,46 @@ def test_resolve_moa_preset_returns_requested_model_set():
]
def test_resolve_missing_moa_preset_has_actionable_error():
cfg = {
"default_preset": "日常对话-高峰",
"presets": {"日常对话-高峰": {}, "日常对话-非高峰": {}},
}
with pytest.raises(MoAPresetNotFoundError) as exc_info:
resolve_moa_preset(cfg, "日常对话-高峰期")
message = str(exc_info.value)
assert "日常对话-高峰期" in message
assert "日常对话-高峰" in message
assert "日常对话-非高峰" in message
assert "hermes moa list" in message
def test_resolve_missing_moa_preset_does_not_silently_fallback():
cfg = {
"default_preset": "日常对话-高峰",
"presets": {"日常对话-高峰": {}},
}
with pytest.raises(MoAPresetNotFoundError):
resolve_moa_preset(cfg, "renamed-preset")
def test_missing_moa_preset_is_non_retryable():
from agent.error_classifier import FailoverReason, classify_api_error
result = classify_api_error(
MoAPresetNotFoundError("MoA preset 'old' was not found"),
provider="moa",
model="old",
)
assert result.reason == FailoverReason.model_not_found
assert result.retryable is False
assert result.should_fallback is False
def test_build_moa_turn_prompt_encodes_one_shot_default_preset():
prompt = build_moa_turn_prompt("write a file then inspect it")