feat(auth): make xAI Grok OAuth device-code-only, drop loopback login

Replace the loopback/PKCE-callback server and manual-paste fallback with
the RFC 8628 device-code flow as the only xAI Grok OAuth login path. The
flow works in headless/SSH/container sessions with no 127.0.0.1 listener,
shrinking the local attack surface.

- Poll the token endpoint with server-provided interval, honoring
  slow_down and expires_in; store tokens with auth_mode
  oauth_device_code.
- Adaptive proactive refresh skew for short-lived device-code JWTs;
  rotated tokens sync back to auth.json, the global root store, and the
  credential pool (no refresh-token replay).
- Clear source suppression on successful re-login (CLI + dashboard) and
  drop the duplicate dashboard pool entry so exactly one seeded
  device_code entry exists.
- Use the shared device_code source name for consistency with the
  nous/codex device-code providers.
- Desktop: remove the loopback OAuth flow states and dead type variants;
  pkce providers' sign-in URL selection is unchanged.
- Docs (EN + zh-Hans) rewritten for device-code login; drop the deleted
  --manual-paste flag from documented commands.
This commit is contained in:
Jaaneek 2026-07-02 15:35:06 +00:00 committed by Teknium
parent 472d75193f
commit 5ef0b8acb0
38 changed files with 733 additions and 2904 deletions

View file

@ -567,12 +567,7 @@ def _model_flow_xai_oauth(_config, current_model="", *, args=None):
print("Starting a fresh xAI OAuth login...")
print()
try:
# Forward CLI flags from ``hermes model --manual-paste``
# / ``--no-browser`` / ``--timeout`` into the loopback
# login. Without this, browser-only remotes (#26923)
# can't reach the manual-paste path via ``hermes model``.
mock_args = argparse.Namespace(
manual_paste=bool(getattr(args, "manual_paste", False)),
no_browser=bool(getattr(args, "no_browser", False)),
timeout=getattr(args, "timeout", None),
)
@ -594,7 +589,6 @@ def _model_flow_xai_oauth(_config, current_model="", *, args=None):
print()
try:
mock_args = argparse.Namespace(
manual_paste=bool(getattr(args, "manual_paste", False)),
no_browser=bool(getattr(args, "no_browser", False)),
timeout=getattr(args, "timeout", None),
)