mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
fix(types): batch P1 ty hotfixes + run_agent.py annotation pass
15 P1 ship-stopper runtime bugs from the ty triage plus the cross-bucket cleanup in run_agent.py. Net: -138 ty diagnostics (1953 -> 1815). Major wins on not-subscriptable (-34), unresolved-attribute (-29), invalid-argument-type (-26), invalid-type-form (-20), unsupported-operator (-18), invalid-key (-9). Missing refs (structural): - tools/rl_training_tool.py: RunState dataclass gains api_log_file, trainer_log_file, env_log_file fields; stop-run was closing undeclared handles. - agent/credential_pool.py: remove_entry(entry_id) added, symmetric with add_entry; used by hermes_cli/web_server.py OAuth dashboard cleanup. - hermes_cli/config.py: _CamofoxConfig TypedDict defined (was referenced by _BrowserConfig but never declared). - hermes_cli/gateway.py: _setup_wecom_callback() added, mirroring _setup_wecom(). - tui_gateway/server.py: skills_hub imports corrected from hermes_cli.skills_hub -> tools.skills_hub. Typo / deprecation: - tools/transcription_tools.py: os.sys.modules -> sys.modules. - gateway/platforms/bluebubbles.py: datetime.utcnow() -> datetime.now(timezone.utc). None-guards: - gateway/platforms/telegram.py:~2798 - msg.sticker None guard. - gateway/platforms/discord.py:3602/3637 - interaction.data None + SelectMenu narrowing; :3009 - thread_id None before `in`; :1893 - guild.member_count None. - gateway/platforms/matrix.py:2174/2185 - walrus-narrow re.search().group(). - agent/display.py:732 - start_time None before elapsed subtraction. - gateway/run.py:10334 - assert _agent_timeout is not None before `// 60`. Platform override signature match: - gateway/platforms/email.py: send_image accepts metadata kwarg; send_document accepts **kwargs (matches base class). run_agent.py annotation pass: - callable/any -> Callable/Any in annotation position (15 sites in run_agent.py + 5 in cli.py, toolset_distributions.py, tools/delegate_tool.py, hermes_cli/dingtalk_auth.py, tui_gateway/server.py). - conversation_history param widened to list[dict[str, Any]] | None. - OMIT_TEMPERATURE sentinel guarded from leaking into call_llm(temperature): kwargs-dict pattern at run_agent.py:7337 + scripts/trajectory_compressor.py:618/688. - build_anthropic_client(timeout) widened to Optional[float]. Tests: - tests/agent/test_credential_pool.py: remove_entry (id match, unknown-id, priority renumbering). - tests/hermes_cli/test_config_shapes.py: _CamofoxConfig shape + nesting. - tests/tools/test_rl_training_tool.py: RunState log_file fields.
This commit is contained in:
parent
fb6d37495b
commit
15ac253b11
24 changed files with 1726 additions and 254 deletions
|
|
@ -1162,3 +1162,79 @@ def test_load_pool_does_not_seed_qwen_oauth_when_no_token(tmp_path, monkeypatch)
|
|||
|
||||
assert not pool.has_credentials()
|
||||
assert pool.entries() == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests for CredentialPool.remove_entry (added in commit fc00f699)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_pool_with_entries(tmp_path, monkeypatch, provider="openrouter", entries=None):
|
||||
"""Helper: build a CredentialPool directly without seeding side-effects."""
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
|
||||
monkeypatch.setattr("agent.credential_pool._seed_from_singletons", lambda p, e: (False, set()))
|
||||
monkeypatch.setattr("agent.credential_pool._seed_from_env", lambda p, e: (False, set()))
|
||||
if entries is None:
|
||||
entries = [
|
||||
{
|
||||
"id": "cred-1",
|
||||
"label": "primary",
|
||||
"auth_type": "api_key",
|
||||
"priority": 0,
|
||||
"source": "manual",
|
||||
"access_token": "tok-1",
|
||||
},
|
||||
{
|
||||
"id": "cred-2",
|
||||
"label": "secondary",
|
||||
"auth_type": "api_key",
|
||||
"priority": 1,
|
||||
"source": "manual",
|
||||
"access_token": "tok-2",
|
||||
},
|
||||
]
|
||||
_write_auth_store(tmp_path, {"version": 1, "credential_pool": {provider: entries}})
|
||||
from agent.credential_pool import load_pool
|
||||
return load_pool(provider)
|
||||
|
||||
|
||||
def test_remove_entry_removes_by_id(tmp_path, monkeypatch):
|
||||
"""remove_entry should remove the entry with matching id and return it."""
|
||||
pool = _build_pool_with_entries(tmp_path, monkeypatch)
|
||||
|
||||
removed = pool.remove_entry("cred-1")
|
||||
|
||||
assert removed is not None
|
||||
assert removed.id == "cred-1"
|
||||
remaining_ids = [e.id for e in pool.entries()]
|
||||
assert "cred-1" not in remaining_ids
|
||||
assert "cred-2" in remaining_ids
|
||||
|
||||
|
||||
def test_remove_entry_returns_none_for_unknown_id(tmp_path, monkeypatch):
|
||||
"""remove_entry returns None when no entry matches the given id."""
|
||||
pool = _build_pool_with_entries(tmp_path, monkeypatch)
|
||||
|
||||
result = pool.remove_entry("nonexistent-id")
|
||||
|
||||
assert result is None
|
||||
# Pool should still have both original entries
|
||||
assert len(pool.entries()) == 2
|
||||
|
||||
|
||||
def test_remove_entry_renumbers_priorities(tmp_path, monkeypatch):
|
||||
"""After remove_entry, remaining entries receive sequential priorities 0, 1, ..."""
|
||||
pool = _build_pool_with_entries(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
entries=[
|
||||
{"id": "cred-1", "label": "a", "auth_type": "api_key", "priority": 0, "source": "manual", "access_token": "tok-1"},
|
||||
{"id": "cred-2", "label": "b", "auth_type": "api_key", "priority": 1, "source": "manual", "access_token": "tok-2"},
|
||||
{"id": "cred-3", "label": "c", "auth_type": "api_key", "priority": 2, "source": "manual", "access_token": "tok-3"},
|
||||
],
|
||||
)
|
||||
|
||||
pool.remove_entry("cred-2")
|
||||
|
||||
remaining = sorted(pool.entries(), key=lambda e: e.priority)
|
||||
assert [e.priority for e in remaining] == [0, 1]
|
||||
assert [e.id for e in remaining] == ["cred-1", "cred-3"]
|
||||
|
|
|
|||
42
tests/hermes_cli/test_config_shapes.py
Normal file
42
tests/hermes_cli/test_config_shapes.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Tests for TypedDict shape definitions added in commit fc00f699.
|
||||
|
||||
Verifies that _CamofoxConfig is importable, honours total=False
|
||||
(all fields optional), and nests correctly inside _BrowserConfig.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_camofox_config_is_partial_typeddict():
|
||||
"""_CamofoxConfig should accept zero or more keys (total=False)."""
|
||||
from hermes_cli.config import _CamofoxConfig, _BrowserConfig
|
||||
|
||||
# total=False: constructing with no keys must succeed at runtime
|
||||
cfg_empty: _CamofoxConfig = {}
|
||||
cfg_with_field: _CamofoxConfig = {"managed_persistence": True}
|
||||
|
||||
assert cfg_empty == {}
|
||||
assert cfg_with_field.get("managed_persistence") is True
|
||||
|
||||
|
||||
def test_camofox_config_nested_in_browser_config():
|
||||
"""_CamofoxConfig should be accepted in the camofox slot of _BrowserConfig."""
|
||||
from hermes_cli.config import _CamofoxConfig, _BrowserConfig
|
||||
|
||||
browser: _BrowserConfig = {
|
||||
"inactivity_timeout": 60,
|
||||
"command_timeout": 10,
|
||||
"record_sessions": False,
|
||||
"allow_private_urls": False,
|
||||
"cdp_url": "http://localhost:9222",
|
||||
"camofox": {"managed_persistence": False},
|
||||
}
|
||||
|
||||
assert browser["camofox"].get("managed_persistence") is False
|
||||
|
||||
|
||||
def test_camofox_config_total_false_flag():
|
||||
"""_CamofoxConfig.__total__ must be False (all fields optional)."""
|
||||
from hermes_cli.config import _CamofoxConfig
|
||||
|
||||
assert _CamofoxConfig.__total__ is False
|
||||
|
|
@ -5,6 +5,8 @@ terminates processes, and handles edge cases on failure paths.
|
|||
Inspired by PR #715 (0xbyt4).
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import io
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
|
@ -118,6 +120,34 @@ class TestStopTrainingRunProcesses:
|
|||
trainer.terminate.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests for RunState log_file fields (added in commit fc00f699)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestRunStateLogFileFields:
|
||||
"""Verify api_log_file, trainer_log_file, env_log_file exist with None defaults."""
|
||||
|
||||
def test_log_file_fields_default_none(self):
|
||||
"""All three log_file fields should default to None."""
|
||||
state = _make_run_state()
|
||||
assert state.api_log_file is None
|
||||
assert state.trainer_log_file is None
|
||||
assert state.env_log_file is None
|
||||
|
||||
def test_accepts_file_handle_for_api_log(self):
|
||||
"""api_log_file should accept an open file-like object."""
|
||||
api_log = io.StringIO()
|
||||
state = _make_run_state(api_log_file=api_log)
|
||||
assert state.api_log_file is api_log
|
||||
|
||||
def test_log_file_fields_present_in_dataclass(self):
|
||||
"""All three field names must be declared on the RunState dataclass."""
|
||||
field_names = {f.name for f in dataclasses.fields(RunState)}
|
||||
assert "api_log_file" in field_names
|
||||
assert "trainer_log_file" in field_names
|
||||
assert "env_log_file" in field_names
|
||||
|
||||
|
||||
class TestStopTrainingRunStatus:
|
||||
"""Verify status transitions in _stop_training_run."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue