hermes(agent): recognize CLI runtime and shorten restarts
All checks were successful
Tests / Declarative: Post Actions passed: 229

This commit is contained in:
jenkins 2026-08-10 23:28:35 -03:00
parent e277e43633
commit 04bd50ca74
3 changed files with 79 additions and 1 deletions

View File

@ -131,7 +131,29 @@ spec:
upsert_env GIT_ASKPASS /opt/coordinator/gitea_askpass.sh upsert_env GIT_ASKPASS /opt/coordinator/gitea_askpass.sh
upsert_env GIT_TERMINAL_PROMPT 0 upsert_env GIT_TERMINAL_PROMPT 0
chmod 0600 "${env_file}" chmod 0600 "${env_file}"
chown -R 10000:10000 /opt/data # Existing owner data is already written as uid/gid 10000. A
# recursive chown made every routine rollout walk the full 20Gi
# workspace while the dashboard had no endpoint. Own only the
# paths this init container creates or updates.
chown 10000:10000 \
/opt/data \
/opt/data/home \
/opt/data/home/.claude \
/opt/data/home/.codex \
/opt/data/home/.kube \
/opt/data/cli-lanes \
/opt/data/logs \
/opt/data/tools \
/opt/data/tools/bin \
/opt/data/workspace \
/opt/data/workspace/coordinator \
/opt/data/workspace/projects \
/opt/data/workspace/skills \
/opt/data/config.yaml \
/opt/data/SOUL.md \
/opt/data/workspace/AGENTS.md \
/opt/data/workspace/START-HERE.md \
"${env_file}"
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
runAsUser: 0 runAsUser: 0

View File

@ -6,6 +6,8 @@ from __future__ import annotations
import copy import copy
import os import os
import re import re
import shutil
import subprocess
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any, Iterable from typing import Any, Iterable
@ -262,6 +264,28 @@ def _existing_model(config: dict[str, Any], provider: str, default: str) -> str:
return default return default
def codex_cli_authenticated() -> bool:
"""Return whether the installed Codex CLI has a usable local login."""
codex = shutil.which("codex")
if codex:
try:
status = subprocess.run(
[codex, "login", "status"],
capture_output=True,
check=False,
text=True,
timeout=10,
)
detail = f"{status.stdout}\n{status.stderr}".lower()
if status.returncode == 0 and (
"logged in" in detail or "authenticated" in detail
):
return True
except (OSError, subprocess.SubprocessError):
pass
return False
def discover_codex_models() -> Catalog: def discover_codex_models() -> Catalog:
"""Use the authenticated Codex endpoint; catalogs are status-only fallback.""" """Use the authenticated Codex endpoint; catalogs are status-only fallback."""
token = "" token = ""
@ -288,6 +312,20 @@ def discover_codex_models() -> Catalog:
known = _unique_models(provider_model_ids("openai-codex", force_refresh=True)) known = _unique_models(provider_model_ids("openai-codex", force_refresh=True))
except Exception: except Exception:
known = [] known = []
# The owner agent deliberately uses the local Codex app-server and the
# Codex CLI's ChatGPT login. That credential is not exported into Hermes'
# bearer-token store, so API discovery above may be empty even though the
# runtime is fully authenticated. Treat a successful CLI status check as a
# connected, degraded catalog: routing may use Codex while retaining the
# last-known working model names until app-server discovery is available.
if codex_cli_authenticated():
return Catalog(
"openai-codex",
known,
False,
True,
"connected-cli",
)
state = "degraded" if token else "not-configured" state = "degraded" if token else "not-configured"
return Catalog("openai-codex", known, False, bool(token), state) return Catalog("openai-codex", known, False, bool(token), state)

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import importlib.util import importlib.util
import json import json
import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@ -64,6 +65,23 @@ def test_empty_catalog_retains_current_models():
assert routing.choose_claude_model([], "claude-opus-5") == "claude-opus-5" assert routing.choose_claude_model([], "claude-opus-5") == "claude-opus-5"
def test_codex_cli_login_counts_as_connected_runtime(monkeypatch):
"""AUTO routing must recognize the authenticated app-server CLI lane."""
monkeypatch.setattr(routing.shutil, "which", lambda name: "/usr/bin/codex")
monkeypatch.setattr(
routing.subprocess,
"run",
lambda *args, **kwargs: subprocess.CompletedProcess(
args[0],
0,
stdout="Logged in using ChatGPT\n",
stderr="",
),
)
assert routing.codex_cli_authenticated() is True
def test_configure_routes_builds_cross_provider_fallback_profiles(tmp_path: Path): def test_configure_routes_builds_cross_provider_fallback_profiles(tmp_path: Path):
(tmp_path / "config.yaml").write_text( (tmp_path / "config.yaml").write_text(
yaml.safe_dump(_base_config()), encoding="utf-8" yaml.safe_dump(_base_config()), encoding="utf-8"