diff --git a/agent/credential_pool_admin.py b/agent/credential_pool_admin.py index 39c6cbedb3..0dd6482043 100644 --- a/agent/credential_pool_admin.py +++ b/agent/credential_pool_admin.py @@ -8,7 +8,24 @@ if TYPE_CHECKING: from agent.credential_pool import PooledCredential +def _cleared_status_copy(entry: PooledCredential) -> PooledCredential: + from agent.credential_pool import _CLEAR_STATUS + + return replace(entry, **_CLEAR_STATUS, + extra={k: v for k, v in entry.extra.items() if k != "failure_reason"}) + + class CredentialPoolAdminMixin: + def reset_status(self, credential_id: str) -> Optional[PooledCredential]: + """Clear only the target's local error state, preserving sibling cooldowns.""" + with self._lock: + entry = self._find(lambda e: e.id == credential_id) + if entry is None: + return None + cleared = _cleared_status_copy(entry) + self._replace_entry(entry, cleared) + self._persist(status_cleared_ids=[cleared.id]) + return cleared def reset_statuses(self) -> int: """Clear exhaustion state on every entry. Returns how many were cleared. @@ -27,11 +44,7 @@ class CredentialPoolAdminMixin: if stale: stale_ids = {e.id for e in stale} self._entries = [ - replace( - e, **_CLEAR_STATUS, - extra={k: v for k, v in e.extra.items() if k != "failure_reason"}, - ) - if e.id in stale_ids else e + _cleared_status_copy(e) if e.id in stale_ids else e for e in self._entries ] self._persist(status_cleared_ids=list(stale_ids)) diff --git a/hermes_cli/_parser.py b/hermes_cli/_parser.py index e87c5e6a74..37ff7a8e09 100644 --- a/hermes_cli/_parser.py +++ b/hermes_cli/_parser.py @@ -73,7 +73,7 @@ Examples: hermes auth add Add a pooled credential hermes auth list List pooled credentials hermes auth remove

Remove pooled credential by index, id, or label - hermes auth reset Clear exhaustion status for a provider + hermes auth reset

[t] Clear exhaustion status for a provider, or one credential hermes model Select default model hermes fallback [list] Show fallback provider chain hermes fallback add Add a fallback provider (same picker as `hermes model`) diff --git a/hermes_cli/auth_commands.py b/hermes_cli/auth_commands.py index 5fdaca1fa1..4da1852e19 100644 --- a/hermes_cli/auth_commands.py +++ b/hermes_cli/auth_commands.py @@ -446,9 +446,19 @@ def auth_remove_command(args) -> None: def auth_reset_command(args) -> None: provider = _normalize_provider(getattr(args, "provider", "")) + target = getattr(args, "target", None) pool = load_pool(provider) - count = pool.reset_statuses() - print(f"Reset status on {count} {provider} credentials") + if target is None or not str(target).strip(): + count = pool.reset_statuses() + print(f"Reset status on {count} {provider} credentials") + return + index, matched, error = pool.resolve_target(target) + if matched is None or index is None: + raise SystemExit(f"{error} Provider: {provider}.") + cleared = pool.reset_status(matched.id) + if cleared is None: + raise SystemExit(f'No credential matching "{target}" for provider {provider}.') + print(f"Reset status on {provider} credential #{index} ({cleared.label})") def auth_status_command(args) -> None: diff --git a/hermes_cli/subcommands/auth.py b/hermes_cli/subcommands/auth.py index 17eb56bd91..62e4d5d151 100644 --- a/hermes_cli/subcommands/auth.py +++ b/hermes_cli/subcommands/auth.py @@ -34,8 +34,11 @@ def build_auth_parser(subparsers, *, cmd_auth: Callable) -> None: auth_remove.add_argument("provider", help="Provider id") auth_remove.add_argument("target", help="Credential index, entry id, or exact label") auth_reset = auth_subparsers.add_parser( - "reset", help="Clear exhaustion status for all credentials for a provider") + "reset", help="Clear exhaustion status for a provider's credentials (all, or one target)") auth_reset.add_argument("provider", help="Provider id") + auth_reset.add_argument( + "target", nargs="?", + help="Optional credential index, entry id, or exact label; clears every credential when omitted") auth_status = auth_subparsers.add_parser("status", help="Show auth status for a provider") auth_status.add_argument("provider", help="Provider id") auth_logout = auth_subparsers.add_parser( diff --git a/hermes_cli/tips.py b/hermes_cli/tips.py index 68397a957f..7cf29f589e 100644 --- a/hermes_cli/tips.py +++ b/hermes_cli/tips.py @@ -337,7 +337,7 @@ TIPS = [ "hermes curator pin hard-fences a skill against both auto-archival and the agent's skill_manage tool.", 'hermes curator rollback restores skills from a pre-run snapshot — backups live under skills/.curator_backups/.', # --- Credential Pools & Routing --- - 'hermes auth reset clears all cooldowns and exhaustion flags on a credential pool.', + 'hermes auth reset [target] clears cooldowns and exhaustion flags on a whole credential pool, or on one credential by index, id, or label.', 'credential_pool_strategies.: round_robin cycles keys evenly instead of the fill_first default.', 'use_gateway: true per-tool routes web, image, tts, or browser through your Nous subscription — no extra keys.', 'provider_routing.data_collection: deny excludes data-storing providers on OpenRouter.', diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 25a400ece1..0a48663380 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -583,6 +583,7 @@ hermes auth add openrouter --api-key sk-or-v1-xxx # Add API key hermes auth add anthropic --type oauth # Add OAuth credential hermes auth remove openrouter 2 # Remove by index hermes auth reset openrouter # Clear cooldowns +hermes auth reset openrouter 2 # Clear the cooldown on one credential hermes auth status anthropic # Show auth status for a provider hermes auth logout anthropic # Log out and clear stored auth state hermes auth spotify # Authenticate Hermes with Spotify via PKCE diff --git a/website/docs/user-guide/features/credential-pools.md b/website/docs/user-guide/features/credential-pools.md index 1b17999373..cedaaa89a4 100644 --- a/website/docs/user-guide/features/credential-pools.md +++ b/website/docs/user-guide/features/credential-pools.md @@ -118,6 +118,7 @@ Type [1/2]: | `hermes auth add --type oauth` | Add an OAuth credential via browser login | | `hermes auth remove ` | Remove credential by 1-based index | | `hermes auth reset ` | Clear all cooldowns/exhaustion status | +| `hermes auth reset ` | Clear the cooldown on one credential by index, id, or label | ## Rotation Strategies