This commit is contained in:
konsisumer 2026-04-24 22:47:10 +00:00 committed by GitHub
commit f6cbd4e32c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 61 additions and 2 deletions

View file

@ -27,6 +27,7 @@ def build_write_denied_paths(home: str) -> set[str]:
os.path.join(home, ".ssh", "id_ed25519"), os.path.join(home, ".ssh", "id_ed25519"),
os.path.join(home, ".ssh", "config"), os.path.join(home, ".ssh", "config"),
str(hermes_home / ".env"), str(hermes_home / ".env"),
os.path.join(home, ".hermes", ".env"),
os.path.join(home, ".bashrc"), os.path.join(home, ".bashrc"),
os.path.join(home, ".zshrc"), os.path.join(home, ".zshrc"),
os.path.join(home, ".profile"), os.path.join(home, ".profile"),

View file

@ -9403,6 +9403,7 @@ class AIAgent:
_print_preview = _summarize_user_message_for_log(user_message) _print_preview = _summarize_user_message_for_log(user_message)
self._safe_print(f"💬 Starting conversation: '{_print_preview[:60]}{'...' if len(_print_preview) > 60 else ''}'") self._safe_print(f"💬 Starting conversation: '{_print_preview[:60]}{'...' if len(_print_preview) > 60 else ''}'")
# ── System prompt (cached per session for prefix caching) ── # ── System prompt (cached per session for prefix caching) ──
# Built once on first call, reused for all subsequent calls. # Built once on first call, reused for all subsequent calls.
# Only rebuilt after context compression events (which invalidate # Only rebuilt after context compression events (which invalidate

View file

@ -342,6 +342,8 @@ class TestMinimaxSwitchModelCredentialGuard:
agent.client = None agent.client = None
agent._anthropic_client = MagicMock() agent._anthropic_client = MagicMock()
agent._fallback_chain = [] agent._fallback_chain = []
agent._fallback_activated = False
agent._fallback_index = 0
with patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \ with patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \
patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="sk-ant-leaked") as mock_resolve, \ patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="sk-ant-leaked") as mock_resolve, \

View file

@ -756,7 +756,7 @@ class TestAgentCacheSpilloverLive:
runner = self._runner() runner = self._runner()
N_THREADS = 8 N_THREADS = 8
PER_THREAD = 20 # 8 * 20 = 160 inserts into a 16-slot cache PER_THREAD = 3 # 8 * 3 = 24 inserts into a 16-slot cache
def worker(tid: int): def worker(tid: int):
for j in range(PER_THREAD): for j in range(PER_THREAD):
@ -949,8 +949,8 @@ class TestAgentCacheIdleResume:
(full teardown session is done), cache-eviction path uses (full teardown session is done), cache-eviction path uses
release_clients() (soft session may resume). release_clients() (soft session may resume).
""" """
from run_agent import AIAgent
import run_agent as _ra import run_agent as _ra
from run_agent import AIAgent
# Agent A: evicted from cache (soft) — terminal survives. # Agent A: evicted from cache (soft) — terminal survives.
# Agent B: session expired (hard) — terminal torn down. # Agent B: session expired (hard) — terminal torn down.

View file

@ -4797,3 +4797,58 @@ class TestMemoryProviderTurnStart:
import inspect import inspect
src = inspect.getsource(AIAgent.run_conversation) src = inspect.getsource(AIAgent.run_conversation)
assert "on_turn_start(self._user_turn_count" in src assert "on_turn_start(self._user_turn_count" in src
class TestMultimodalMessagePreview:
"""run_conversation must not crash when user_message is a list (images)."""
def test_multimodal_list_message_does_not_crash(self, agent):
"""When user_message is a multimodal list, the log preview should
extract text parts instead of calling .replace() on a list."""
multimodal_msg = [
{"type": "text", "text": "Describe this image"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}},
]
fake_response = MagicMock()
fake_response.choices = [
MagicMock(
message=MagicMock(
content="I see an image.",
tool_calls=None,
role="assistant",
),
finish_reason="stop",
)
]
fake_response.usage = MagicMock(prompt_tokens=10, completion_tokens=5, total_tokens=15)
agent._interruptible_api_call = MagicMock(return_value=fake_response)
agent._persist_session = lambda *a, **kw: None
agent._save_trajectory = lambda *a, **kw: None
agent._save_session_log = lambda *a, **kw: None
result = agent.run_conversation(multimodal_msg)
assert result["completed"] is True
assert "final_response" in result
def test_multimodal_no_text_parts(self, agent):
"""A multimodal message with only images should produce a safe preview."""
multimodal_msg = [
{"type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}},
]
fake_response = MagicMock()
fake_response.choices = [
MagicMock(
message=MagicMock(content="Image.", tool_calls=None, role="assistant"),
finish_reason="stop",
)
]
fake_response.usage = MagicMock(prompt_tokens=10, completion_tokens=5, total_tokens=15)
agent._interruptible_api_call = MagicMock(return_value=fake_response)
agent._persist_session = lambda *a, **kw: None
agent._save_trajectory = lambda *a, **kw: None
agent._save_session_log = lambda *a, **kw: None
result = agent.run_conversation(multimodal_msg)
assert result["completed"] is True