mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
ci: remove unused variables (linter)
This commit is contained in:
1 parent
7ef490ed3b
commit
bda3aea496
117 files changed
+84
-185
No files matched your search
@@ -129,7 +129,6 @@ def radon(files):
|
||||
R = shutil.which("radon") or os.path.join(os.path.dirname(sys.executable), "radon")
|
||||
import tempfile
|
||||
out = {}
|
||||
lst = "\n".join(files)
|
||||
# radon can't take a list file; run per-directory roots instead
|
||||
roots = sorted({os.path.relpath(f, TREE).split(os.sep)[0] if os.sep in os.path.relpath(f, TREE) else os.path.relpath(f, TREE) for f in files})
|
||||
roots = [r for r in roots if r != "tests"]
|
||||
|
||||
@@ -428,7 +428,6 @@ def main():
|
||||
|
||||
summaries = []
|
||||
if args.also_uncompacted:
|
||||
spec = {"ctor": {}, "attrs": {"tail_token_budget": 10**9}}
|
||||
# control: no compression at all — answer from the full transcript
|
||||
context_text = serialize_for_exam(messages, char_cap=900_000)
|
||||
results = []
|
||||
|
||||
@@ -61,9 +61,6 @@ class Peer(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
||||
requests.append({"path": self.path, "body": body})
|
||||
users = [
|
||||
m.get("content") for m in body.get("messages", []) if m["role"] == "user"
|
||||
]
|
||||
fail = fault == "http400"
|
||||
if fail:
|
||||
data = {
|
||||
|
||||
@@ -131,7 +131,6 @@ class _OAStream:
|
||||
def patched_create(self, *a, **kw):
|
||||
msgs = kw.get("messages") or []
|
||||
import re as _re
|
||||
first = msgs[0] if msgs else {}
|
||||
sysm = next((m for m in msgs if m.get("role") == "system"), None)
|
||||
nonsys = [m for m in msgs if m.get("role") != "system"]
|
||||
f0 = nonsys[0] if nonsys else {}
|
||||
|
||||
@@ -28,12 +28,11 @@ def claims(token):
|
||||
records=[]
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
raw_body=self.rfile.read(int(self.headers.get('Content-Length',0)))
|
||||
self.rfile.read(int(self.headers.get('Content-Length',0)))
|
||||
if self.path == '/api/oauth/token':
|
||||
records.append({'path':self.path,'refresh':True})
|
||||
raw=json.dumps({'access_token':refresh_reply,'refresh_token':'fixture-rotated','expires_in':3600,'token_type':'Bearer','scope':'inference:invoke'}).encode()
|
||||
self.send_response(200);self.send_header('Content-Type','application/json');self.send_header('Content-Length',str(len(raw)));self.end_headers();self.wfile.write(raw);return
|
||||
body=json.loads(raw_body or '{}')
|
||||
bearer=self.headers.get('Authorization','').removeprefix('Bearer ')
|
||||
records.append({'path':self.path,'sub':claims(bearer).get('sub') if bearer else None})
|
||||
if bearer and claims(bearer)['exp'] < time.time():
|
||||
|
||||
@@ -124,7 +124,6 @@ def run(out_path: str) -> dict:
|
||||
cc.context_length = CONTEXT_LENGTH
|
||||
cc.threshold_tokens = THRESHOLD
|
||||
compress_calls: list[dict] = []
|
||||
original = agent._compress_context
|
||||
|
||||
def counting(messages, system_message, **kw):
|
||||
# Real compaction would need a summarizer; drop everything but the last 2 rows like one.
|
||||
|
||||
@@ -62,7 +62,7 @@ def run_one(scenario: dict[str, Any], mode: str, rep: int, out_dir: Path) -> dic
|
||||
hermes_home = base.setup_isolated_home(enabled, listing=("auto" if mode == "listing" else "off"))
|
||||
os.environ["HERMES_HOME"] = str(hermes_home)
|
||||
base.reset_module_state()
|
||||
n_registered = base.register_fake_tools()
|
||||
base.register_fake_tools()
|
||||
|
||||
base.FIXTURE_NOTES.parent.mkdir(parents=True, exist_ok=True)
|
||||
base.FIXTURE_NOTES.write_text("Hello from the test fixture.\n", encoding="utf-8")
|
||||
|
||||
@@ -184,7 +184,6 @@ def run_one(scenario, mode, scale, rep, out_dir: Path):
|
||||
error = None
|
||||
final_response = ""
|
||||
messages_out: list[dict[str, Any]] = []
|
||||
pm = None
|
||||
_orig_norm = None
|
||||
try:
|
||||
from run_agent import AIAgent
|
||||
|
||||
@@ -326,7 +326,6 @@ class _Collector:
|
||||
"""Claim events on one board, appending delivery dicts to ``deliveries``."""
|
||||
if not self._board_has_subs(slug):
|
||||
return
|
||||
kb = self.kb
|
||||
try:
|
||||
conn = _kbc().connect(board=slug)
|
||||
except Exception as exc:
|
||||
@@ -791,7 +790,7 @@ class _KanbanNotification:
|
||||
if self.wake_kinds:
|
||||
wake_payloads.append((self.synth, self.wake_diagnostic, self.wake_kinds))
|
||||
self.d = {**self.d, "events": original_events}
|
||||
wake_kinds, is_push = self.wake_kinds, self.is_push_adapter
|
||||
is_push = self.is_push_adapter
|
||||
from gateway.wake import WakeNotAccepted
|
||||
|
||||
# A requested wake is required even when its passive ping already landed.
|
||||
|
||||
+1
-1
@@ -2537,7 +2537,7 @@ class GatewayTurnMixin:
|
||||
)
|
||||
await sender(chat_id=source.chat_id, metadata=_thread_metadata, **{key: media_path})
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.exception("Background task %s failed", task_id)
|
||||
# Automatic failure diagnostic (the task produced no requested result to deliver).
|
||||
with suppress(Exception):
|
||||
|
||||
@@ -867,7 +867,7 @@ def _start_or_report_running(running_pids: list[int] | None = None) -> None:
|
||||
if running_pids:
|
||||
_report_already_running(running_pids)
|
||||
else:
|
||||
pid = _spawn_detached()
|
||||
_spawn_detached()
|
||||
_report_gateway_start("direct spawn")
|
||||
|
||||
|
||||
@@ -1689,7 +1689,7 @@ def start() -> None:
|
||||
|
||||
# Manual starts use the same console-less direct spawn as restart() and install --start-now;
|
||||
# Scheduled Task / Startup entries are only login persistence.
|
||||
pid = _spawn_detached()
|
||||
_spawn_detached()
|
||||
_report_gateway_start("direct spawn")
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class TestCloudCheckDepsLive:
|
||||
class TestHealthCheckLive:
|
||||
def test_health_check_passes(self, cloud_key, capsys):
|
||||
from health_check import main as health_main
|
||||
rc = health_main(["--host", "https://cloud.comfy.org", "--api-key", cloud_key])
|
||||
health_main(["--host", "https://cloud.comfy.org", "--api-key", cloud_key])
|
||||
captured = capsys.readouterr()
|
||||
# Should produce JSON
|
||||
import json
|
||||
|
||||
@@ -176,7 +176,6 @@ class TestDuplicateDeduplication:
|
||||
schema = extract_schema(wf)
|
||||
params = schema["parameters"]
|
||||
# Both seeds present with disambiguated names
|
||||
seed_keys = [k for k in params if "seed" in k]
|
||||
# Symmetric: both renamed (no bare "seed")
|
||||
assert "seed" not in params
|
||||
assert "seed_3" in params and "seed_4" in params
|
||||
|
||||
@@ -151,7 +151,6 @@ def render_brief(plan: dict) -> str:
|
||||
f"| {d.get('format', '?')} | {d.get('resolution', '?')} | "
|
||||
f"{d.get('notes', '')} |"
|
||||
)
|
||||
deliv_table = "\n".join(deliv_rows) if deliv_rows else "_(none)_"
|
||||
|
||||
# Replacements (single-pass)
|
||||
replacements = {
|
||||
|
||||
@@ -228,7 +228,6 @@ def cmd_trades(limit: int = 10, market: str | None = None):
|
||||
size = t.get("size", "?")
|
||||
outcome = t.get("outcome", "?")
|
||||
title = t.get("title", "?")[:50]
|
||||
ts = t.get("timestamp", "")
|
||||
print(f" {side:4} {price:>7} x{float(size):>8.2f} [{outcome}] {title}")
|
||||
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ def _build_request(url: str, headers: dict | None = None) -> urllib.request.Requ
|
||||
|
||||
def fetch_url(url: str, headers: dict | None = None, retries: int = MAX_RETRIES) -> dict | list | None:
|
||||
"""Fetch a URL, parse JSON, retry on transient errors."""
|
||||
last_err = None
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
req = _build_request(url, headers)
|
||||
@@ -124,17 +123,14 @@ def fetch_url(url: str, headers: dict | None = None, retries: int = MAX_RETRIES)
|
||||
raw = resp.read()
|
||||
return json.loads(raw.decode("utf-8", errors="replace"))
|
||||
except urllib.error.HTTPError as e:
|
||||
last_err = e
|
||||
if e.code in {404, 400}:
|
||||
break # no point retrying
|
||||
wait = BACKOFF_BASE ** attempt
|
||||
time.sleep(wait)
|
||||
except urllib.error.URLError as e:
|
||||
last_err = e
|
||||
except urllib.error.URLError:
|
||||
wait = BACKOFF_BASE ** attempt
|
||||
time.sleep(wait)
|
||||
except json.JSONDecodeError as e:
|
||||
last_err = e
|
||||
except json.JSONDecodeError:
|
||||
break
|
||||
return None
|
||||
|
||||
@@ -572,7 +568,6 @@ def cmd_compare(symbols: list[str]) -> None:
|
||||
# 52w performance: (current - 52w_low) / (52w_high - 52w_low)
|
||||
try:
|
||||
price_f = float(entry["price"]) if entry["price"] else None
|
||||
high_f = float(entry["52w_high"]) if entry["52w_high"] else None
|
||||
low_f = float(entry["52w_low"]) if entry["52w_low"] else None
|
||||
if price_f and low_f and price_f > 0 and low_f > 0:
|
||||
perf = ((price_f - low_f) / low_f) * 100
|
||||
|
||||
@@ -2364,7 +2364,6 @@ class Migrator:
|
||||
entries = plugins.get("entries") or {}
|
||||
for plugin_name, plugin_cfg in entries.items():
|
||||
if isinstance(plugin_cfg, dict):
|
||||
env_vars = plugin_cfg.get("env") or {}
|
||||
api_key = plugin_cfg.get("apiKey")
|
||||
if api_key and self.migrate_secrets:
|
||||
env_key = f"PLUGIN_{plugin_name.upper().replace('-', '_')}_API_KEY"
|
||||
|
||||
@@ -256,7 +256,6 @@ def _cmd_status(_args: argparse.Namespace) -> int:
|
||||
# credential-derived strings (keeps cli.py taint-free for CodeQL).
|
||||
photon_auth.print_credential_summary(print)
|
||||
node_bin = find_node_executable("node")
|
||||
sidecar_installed = sidecar_deps_installed()
|
||||
print(f" node binary : {node_bin or '✗ missing (install Node 18+)'}")
|
||||
print(f" sidecar deps : {'✓ installed' if sidecar_deps_installed() else '✗ run `hermes photon install-sidecar`'}")
|
||||
print(f" telemetry : {'on' if _telemetry_enabled() else 'off'} (`hermes photon telemetry on|off`)")
|
||||
|
||||
@@ -872,7 +872,6 @@ def drift(*, include_venv: bool = True) -> dict[str, str]:
|
||||
problems: dict[str, str] = {}
|
||||
lockfile = _lockfile()
|
||||
facts = _facts()
|
||||
store = _store()
|
||||
target = current_target()
|
||||
for name in lockfile.names():
|
||||
try:
|
||||
|
||||
+1
-1
@@ -276,7 +276,7 @@ select = [
|
||||
# "F821",
|
||||
"F822",
|
||||
"F823",
|
||||
# "F841",
|
||||
"F841",
|
||||
"F842",
|
||||
"F901",
|
||||
"PGH005",
|
||||
|
||||
@@ -776,7 +776,6 @@ def scan_file(path: Path, footguns: list[Footgun]) -> list[tuple[int, str, Footg
|
||||
continue
|
||||
# Now check for docstring-open in the (possibly after-triple) portion.
|
||||
# Scan for the first unescaped '''/""" in the current code_for_scan.
|
||||
stripped = code_for_scan.strip()
|
||||
for delim in ('"""', "'''"):
|
||||
if delim in code_for_scan:
|
||||
# Count occurrences — even count means single-line docstring,
|
||||
|
||||
@@ -291,7 +291,6 @@ def render_comment(
|
||||
|
||||
info = by_severity.get("info", [])
|
||||
debug = by_severity.get("debug", [])
|
||||
has_blocking = any(by_severity.get(s) for s in _BLOCKING_SEVERITIES)
|
||||
|
||||
body = f"{MARKER}\n# ૮ >ﻌ< ა ci review\n\n"
|
||||
|
||||
|
||||
@@ -788,7 +788,6 @@ def _regressions(timings: dict, baseline: dict | None) -> str:
|
||||
|
||||
rows = []
|
||||
for _, diff, job, step, cur, bl_d in top:
|
||||
cls = "slower" if diff > 0 else "faster"
|
||||
tag = f'<span class="tag {"slow" if diff > 0 else "fast"}">{"+" if diff > 0 else ""}{diff:.1f}s</span>'
|
||||
rows.append(
|
||||
f'<tr>'
|
||||
@@ -860,7 +859,6 @@ def generate_html(timings: dict, baseline: dict | None = None) -> str:
|
||||
|
||||
def generate_summary(timings: dict, baseline: dict | None = None) -> str:
|
||||
stats = compute_stats(timings, baseline)
|
||||
bl_map = {j["name"]: j for j in (baseline or {}).get("jobs", [])}
|
||||
|
||||
lines = ["## CI Timing Summary\n"]
|
||||
|
||||
|
||||
@@ -291,8 +291,6 @@ def collect_salvaged_contributors(since_tag, until="HEAD"):
|
||||
pr_number = pr.get("number", "?")
|
||||
|
||||
# Also credit the PR author
|
||||
pr_author = pr.get("author", {})
|
||||
pr_author_login = pr_author.get("login", "") if isinstance(pr_author, dict) else ""
|
||||
|
||||
for pattern in SALVAGE_PATTERNS:
|
||||
for match in pattern.finditer(body):
|
||||
|
||||
@@ -61,7 +61,6 @@ def main() -> int:
|
||||
outbox_directory=scratch / "outbox",
|
||||
)
|
||||
|
||||
today = datetime.now(UTC).date().isoformat()
|
||||
# The generator only exports COMPLETED periods, so the realistic E2E
|
||||
# package is yesterday's. It also has to be: the consent gate only
|
||||
# releases a package once its whole period is confirmed consented, and
|
||||
|
||||
@@ -967,7 +967,6 @@ def cmd_check(source: Path, out: Path) -> int:
|
||||
for i in range(count):
|
||||
entry = data[6 + i * 16 : 6 + (i + 1) * 16]
|
||||
w = entry[0] or 256
|
||||
h = entry[1] or 256
|
||||
sizes.append(w)
|
||||
return sorted(set(sizes))
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ def read_archive_bytes(key: str) -> bytes:
|
||||
def advance_stable(env: dict, release: dict, root: Path) -> dict:
|
||||
"""Advance one published release from its immutable tag-scoped receipts."""
|
||||
creds, base, bucket = r2.credentials()
|
||||
store = R2ChannelStore(creds, base, bucket)
|
||||
R2ChannelStore(creds, base, bucket)
|
||||
public_base = r2.public_base_url()
|
||||
key = f"releases/tag/{release['claim_tag']}/release-candidates.json"
|
||||
digest = hashlib.sha256(read_archive_bytes(key)).hexdigest()
|
||||
|
||||
@@ -922,7 +922,7 @@ def _load_durations(repo_root: Path) -> dict[str, float]:
|
||||
return {}
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
except (json.JSONDecodeError, OSError):
|
||||
print("[ERROR] Failed to load json durations file! {e}")
|
||||
return {}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ def test_edit_swap_image(deck, workdir):
|
||||
"YPj/HwADAgH/p5UronAAAAAASUVORK5CYII=")
|
||||
blue_path = workdir / "blue.png"
|
||||
blue_path.write_bytes(blue)
|
||||
outline = run("pptx_read.py", deck)
|
||||
run("pptx_read.py", deck)
|
||||
# find picture shape name via python-pptx directly
|
||||
sys.path.insert(0, SCRIPTS)
|
||||
from pptx import Presentation
|
||||
|
||||
@@ -383,7 +383,6 @@ def _live_system_guard(request, monkeypatch):
|
||||
return _GuardedPopen
|
||||
|
||||
real_run = _subprocess.run
|
||||
real_popen = _subprocess.Popen
|
||||
real_call = _subprocess.call
|
||||
real_check_call = _subprocess.check_call
|
||||
real_check_output = _subprocess.check_output
|
||||
|
||||
@@ -936,7 +936,7 @@ class TestPreflightCompression:
|
||||
patch.object(agent, "_save_trajectory"),
|
||||
patch.object(agent, "_cleanup_task_resources"),
|
||||
):
|
||||
result = agent.run_conversation("hello", conversation_history=big_history)
|
||||
agent.run_conversation("hello", conversation_history=big_history)
|
||||
|
||||
mock_compress.assert_not_called()
|
||||
|
||||
|
||||
@@ -444,7 +444,7 @@ class TestStaleAttemptEndToEnd:
|
||||
finally:
|
||||
a_done.set()
|
||||
|
||||
gen1 = _claim_compressor_attempt(cc)
|
||||
_claim_compressor_attempt(cc)
|
||||
with patch("agent.context_compressor.call_llm", side_effect=fake_call_llm):
|
||||
t = threading.Thread(target=attempt_a, daemon=True)
|
||||
thread_a[0] = t
|
||||
|
||||
@@ -56,7 +56,7 @@ def test_prune_only_pass_takes_no_snapshot_but_still_ages_old_ones_out(env, monk
|
||||
|
||||
|
||||
def test_only_one_process_claims_a_due_pass(env, monkeypatch):
|
||||
curator, home = env["curator"], env["home"]
|
||||
curator = env["curator"]
|
||||
monkeypatch.setattr(curator, "should_run_now", lambda now=None: True)
|
||||
started, release = threading.Event(), threading.Event()
|
||||
runs = []
|
||||
|
||||
@@ -69,7 +69,7 @@ from agent.agent_runtime_helpers import sanitize_tool_call_arguments
|
||||
|
||||
def simulate_compression(msgs):
|
||||
"""Rewrite the middle of the history with fresh dict copies + a summary."""
|
||||
head, mid, tail = msgs[:2], msgs[2:-6], msgs[-6:]
|
||||
head, tail = msgs[:2], msgs[-6:]
|
||||
summary = {"role": "user", "content": "SUMMARY OF DROPPED CONTEXT " + UNI}
|
||||
new = [dict(m) if isinstance(m, dict) else m for m in head]
|
||||
new.append(summary)
|
||||
|
||||
@@ -541,7 +541,7 @@ class TestConversationGenerationRotates:
|
||||
assert resolve_prompt_cache_scope(again) == scope_mine
|
||||
|
||||
def test_scope_never_carries_the_raw_key_or_boundary(self, db):
|
||||
agent = self._keyed(db, "sess-A")
|
||||
self._keyed(db, "sess-A")
|
||||
db.end_session("sess-A", "session_reset")
|
||||
rotated = self._keyed(db, "sess-B")
|
||||
scope = resolve_prompt_cache_scope(rotated)
|
||||
|
||||
@@ -74,7 +74,6 @@ class TestCompressNoOpRegistersIneffective:
|
||||
|
||||
# Mock _find_tail_cut_by_tokens to return head_end,
|
||||
# causing compress_start >= compress_end
|
||||
original = comp._find_tail_cut_by_tokens
|
||||
comp._find_tail_cut_by_tokens = lambda msgs, he: he # force no-op
|
||||
|
||||
result = comp.compress(messages, current_tokens=73_000)
|
||||
|
||||
@@ -222,7 +222,6 @@ class TestInsightsPopulated:
|
||||
"""A session that switches models mid-flight is split across both
|
||||
models in the breakdown, not dumped on the initial model (#51607).
|
||||
"""
|
||||
now = time.time()
|
||||
db.create_session(session_id="sw", source="cli",
|
||||
model="deepseek/deepseek-v4-pro")
|
||||
# 40k tokens on deepseek, then switch and 50k on opus.
|
||||
@@ -534,7 +533,7 @@ class TestEdgeCases:
|
||||
assert report["platforms"][0]["platform"] == "cli"
|
||||
|
||||
# Terminal format should NOT show platform section for single platform
|
||||
text = engine.format_terminal(report)
|
||||
engine.format_terminal(report)
|
||||
# (it still shows platforms section if there's only cli and nothing else)
|
||||
# Actually the condition is > 1 platforms OR non-cli, so single cli won't show
|
||||
|
||||
|
||||
@@ -46,5 +46,5 @@ def test_create_openai_client_preserves_already_suffixed_kimi_url(monkeypatch):
|
||||
"base_url": "https://api.kimi.com/coding/v1",
|
||||
}
|
||||
|
||||
client = create_openai_client(mock_agent, client_kwargs, reason="test", shared=False)
|
||||
create_openai_client(mock_agent, client_kwargs, reason="test", shared=False)
|
||||
assert captured_kwargs.get("base_url") == "https://api.kimi.com/coding/v1"
|
||||
@@ -98,7 +98,6 @@ class TestQueryLocalContextLengthVllm:
|
||||
from agent.model_metadata import _query_local_context_length
|
||||
|
||||
detail_resp = self._make_resp(200, {"id": "omnicoder-9b", "max_model_len": 100000})
|
||||
list_resp = self._make_resp(404, {})
|
||||
|
||||
client_mock = MagicMock()
|
||||
client_mock.__enter__ = lambda s: client_mock
|
||||
@@ -285,7 +284,6 @@ class TestQueryLocalContextLengthModelsList:
|
||||
"""
|
||||
from agent.model_metadata import _query_local_context_length
|
||||
|
||||
detail_resp = self._make_resp(404, {})
|
||||
list_resp = self._make_resp(200, {
|
||||
"data": [
|
||||
{
|
||||
@@ -317,7 +315,6 @@ class TestQueryLocalContextLengthModelsList:
|
||||
since the server can only actually serve the runtime value."""
|
||||
from agent.model_metadata import _query_local_context_length
|
||||
|
||||
detail_resp = self._make_resp(404, {})
|
||||
list_resp = self._make_resp(200, {
|
||||
"data": [
|
||||
{"id": "/app/models/m.gguf", "meta": {"n_ctx": 256000, "n_ctx_train": 262144}}
|
||||
@@ -381,7 +378,6 @@ class TestQueryLocalContextLengthAnthropicProxy:
|
||||
def test_models_list_prefers_max_input_tokens(self):
|
||||
from agent.model_metadata import _query_local_context_length
|
||||
|
||||
detail_resp = self._make_resp(404, {})
|
||||
list_resp = self._make_resp(200, {
|
||||
"data": [
|
||||
{
|
||||
@@ -930,7 +926,6 @@ class TestQueryLocalContextLengthMaxTokensNotContext:
|
||||
no genuine context-window key is present."""
|
||||
from agent.model_metadata import _query_local_context_length
|
||||
|
||||
detail_resp = self._make_resp(404, {})
|
||||
list_resp = self._make_resp(200, {
|
||||
"data": [
|
||||
{
|
||||
@@ -1000,7 +995,6 @@ class TestReconcileSelfHealsPoisonedCache:
|
||||
the poisoned 393216 cache entry with 1048576."""
|
||||
from agent.model_metadata import _reconcile_local_cached_context_length
|
||||
|
||||
detail_resp = self._make_resp(404, {})
|
||||
list_resp = self._make_resp(200, {
|
||||
"data": [
|
||||
{
|
||||
|
||||
@@ -725,7 +725,7 @@ class TestAuxiliaryClientProviderPriority:
|
||||
"scope": "inference:invoke",
|
||||
}
|
||||
with patch("agent.auxiliary_client._read_nous_auth", return_value=nous_auth), \
|
||||
patch("agent.auxiliary_client.OpenAI") as mock, \
|
||||
patch("agent.auxiliary_client.OpenAI"), \
|
||||
patch("hermes_cli.models.get_nous_recommended_aux_model", return_value=None):
|
||||
_client, model = get_text_auxiliary_client()
|
||||
assert model == _NOUS_MODEL
|
||||
|
||||
@@ -833,7 +833,7 @@ def test_logical_close_skips_pop_under_concurrent_turn_scope(relay_turn):
|
||||
observe_top = lambda: lease.host.run_in_session(
|
||||
lease.session, relay_runtime._current_top, relay
|
||||
)
|
||||
top_before_sibling = observe_top()
|
||||
observe_top()
|
||||
|
||||
# A concurrent turn's live scope sits above ours.
|
||||
sibling_handle = lease.host.run_in_session(
|
||||
|
||||
@@ -124,7 +124,6 @@ def test_xai_responses_ttfb_scaled_for_large_requests(monkeypatch):
|
||||
# 2. Large request (>50k tokens) scales TTFB timeout to 120s, so it does NOT trip at 0.2s
|
||||
closes.clear()
|
||||
large_text = "word " * 65_000
|
||||
stop_large = {"flag": False}
|
||||
|
||||
def fake_stream_delayed(api_kwargs, client=None, on_first_delta=None):
|
||||
# Sleeps for 0.5s (longer than the initial 0.2s TTFB cutoff)
|
||||
|
||||
@@ -5859,7 +5859,7 @@ class TestAnthropicBaseUrlPassthrough:
|
||||
patch("agent.anthropic_adapter.build_anthropic_client") as mock_build,
|
||||
):
|
||||
mock_build.return_value = MagicMock()
|
||||
a = AIAgent(
|
||||
AIAgent(
|
||||
api_key="sk-ant-api03-test1234567890",
|
||||
base_url="https://llm-proxy.company.com/v1",
|
||||
api_mode="anthropic_messages",
|
||||
|
||||
@@ -1613,7 +1613,7 @@ def test_try_refresh_copilot_client_credentials_falls_back_when_exchange_unavail
|
||||
|
||||
|
||||
def test_preflight_codex_api_kwargs_strips_optional_function_call_id(monkeypatch):
|
||||
agent = _build_agent(monkeypatch)
|
||||
_build_agent(monkeypatch)
|
||||
from agent.codex_responses_adapter import _preflight_codex_api_kwargs
|
||||
preflight = _preflight_codex_api_kwargs(
|
||||
{
|
||||
@@ -1640,7 +1640,7 @@ def test_preflight_codex_api_kwargs_strips_optional_function_call_id(monkeypatch
|
||||
|
||||
|
||||
def test_preflight_codex_api_kwargs_rejects_function_call_output_without_call_id(monkeypatch):
|
||||
agent = _build_agent(monkeypatch)
|
||||
_build_agent(monkeypatch)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
from agent.codex_responses_adapter import _preflight_codex_api_kwargs
|
||||
@@ -1915,7 +1915,7 @@ def test_codex_incomplete_opaque_state_updated_in_place(monkeypatch):
|
||||
|
||||
|
||||
def test_normalize_codex_response_marks_commentary_only_message_as_incomplete(monkeypatch):
|
||||
agent = _build_agent(monkeypatch)
|
||||
_build_agent(monkeypatch)
|
||||
from agent.codex_responses_adapter import _normalize_codex_response
|
||||
assistant_message, finish_reason = _normalize_codex_response(
|
||||
_codex_commentary_message_response("I'll inspect the repository first.")
|
||||
@@ -1930,7 +1930,7 @@ def test_normalize_codex_response_marks_commentary_only_message_as_incomplete(mo
|
||||
|
||||
|
||||
def test_normalize_codex_response_does_not_fallback_to_output_text_for_commentary_only(monkeypatch):
|
||||
agent = _build_agent(monkeypatch)
|
||||
_build_agent(monkeypatch)
|
||||
from agent.codex_responses_adapter import _normalize_codex_response
|
||||
|
||||
response = _codex_commentary_message_response("I’ll call the tool now.")
|
||||
@@ -2187,7 +2187,7 @@ def test_chat_messages_to_responses_input_reasoning_only_has_following_item(monk
|
||||
"""When converting a reasoning-only interim message to Responses API input,
|
||||
the reasoning items must be followed by an assistant message (even if empty)
|
||||
to satisfy the API's 'required following item' constraint."""
|
||||
agent = _build_agent(monkeypatch)
|
||||
_build_agent(monkeypatch)
|
||||
messages = [
|
||||
{"role": "user", "content": "think hard"},
|
||||
{
|
||||
@@ -2445,7 +2445,6 @@ def test_codex_compaction_only_continuation_gets_nudged_before_budget_runs_out(m
|
||||
_codex_message_response("Here is the answer."),
|
||||
]
|
||||
sent_message_counts: list = []
|
||||
original_call = agent._interruptible_api_call
|
||||
|
||||
def _fake_call(api_kwargs):
|
||||
sent_message_counts.append(api_kwargs)
|
||||
|
||||
@@ -172,7 +172,6 @@ class TestAllowlistOps:
|
||||
# Touch the script to bump its mtime then re-approve.
|
||||
import os
|
||||
import time
|
||||
new_mtime = original_entry.get("script_mtime_at_approval")
|
||||
time.sleep(0.01)
|
||||
os.utime(script, None) # current time
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ class TestGatewayAutoLoadScaffold:
|
||||
return f"{joined}\n\n{user_text}" if user_text else joined
|
||||
|
||||
def test_describes_the_typed_request(self, skills):
|
||||
message = self._auto_load_scaffold(skills_dir := skills, ["work"],
|
||||
message = self._auto_load_scaffold(skills, ["work"],
|
||||
user_text="Fix the CI gate before the release")
|
||||
assert describe_skill_invocation(message) == "Fix the CI gate before the release"
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ def test_stderr_is_also_routed_per_thread():
|
||||
def test_many_concurrent_silenced_and_loud_threads():
|
||||
"""Stress: interleaved silenced/loud threads keep their respective fates."""
|
||||
start = threading.Event()
|
||||
results_lock = threading.Lock()
|
||||
|
||||
def silenced(i):
|
||||
start.wait(timeout=2.0)
|
||||
|
||||
@@ -944,7 +944,7 @@ def test_flush_atomic_mixed_repair_and_append_rollback_on_failure(tmp_path, monk
|
||||
agent = _make_agent()
|
||||
db_path = tmp_path / "state.db"
|
||||
session_id = "sess-atomic-rollback"
|
||||
db = _attach_real_session_db(agent, db_path, session_id)
|
||||
_attach_real_session_db(agent, db_path, session_id)
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "summarize"},
|
||||
|
||||
@@ -246,7 +246,7 @@ def test_bounded_fallback_records_kanban_failure_when_interrupted(monkeypatch):
|
||||
agent = _LimitAgent()
|
||||
|
||||
# Budget exhausted (60/60), interrupted, no fallback-eligible exit_reason
|
||||
result = finalize_turn(
|
||||
finalize_turn(
|
||||
agent,
|
||||
final_response=None,
|
||||
api_call_count=60,
|
||||
@@ -286,7 +286,7 @@ def test_bounded_fallback_records_kanban_failure_when_failed(monkeypatch):
|
||||
monkeypatch.setattr("hermes_cli.kanban_db_dispatch._record_task_failure", record)
|
||||
agent = _LimitAgent()
|
||||
|
||||
result = finalize_turn(
|
||||
finalize_turn(
|
||||
agent,
|
||||
final_response=None,
|
||||
api_call_count=60,
|
||||
@@ -319,7 +319,7 @@ def test_bounded_fallback_does_not_fire_without_kanban_task(monkeypatch):
|
||||
monkeypatch.setattr("hermes_cli.kanban_db_dispatch._record_task_failure", record)
|
||||
agent = _LimitAgent()
|
||||
|
||||
result = finalize_turn(
|
||||
finalize_turn(
|
||||
agent,
|
||||
final_response=None,
|
||||
api_call_count=60,
|
||||
@@ -351,7 +351,7 @@ def test_bounded_fallback_does_not_fire_when_budget_not_exhausted(monkeypatch):
|
||||
agent = _LimitAgent(budget_remaining=60)
|
||||
|
||||
# api_call_count=10, max_iterations=60 — budget NOT exhausted
|
||||
result = finalize_turn(
|
||||
finalize_turn(
|
||||
agent,
|
||||
final_response=None,
|
||||
api_call_count=10,
|
||||
|
||||
@@ -234,7 +234,6 @@ class TestYieldedTickIsAFailedTick:
|
||||
ticked: list[str] = []
|
||||
per_home_beats: dict[str, list[bool]] = {home_a: [], home_b: []}
|
||||
per_home_errors: dict[str, list[str]] = {home_a: [], home_b: []}
|
||||
state = {"cycles": 0}
|
||||
stop = threading.Event()
|
||||
prov = InProcessCronScheduler()
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ def _create_job() -> dict:
|
||||
|
||||
class TestCreateSurfacesGatewayLiveness:
|
||||
def test_create_with_gateway_running_has_no_warning(self, hermes_env):
|
||||
with patch_liveness(provider="builtin", pids=[12345]) as patches:
|
||||
with patch_liveness(provider="builtin", pids=[12345]):
|
||||
result = _create_job()
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
@@ -1299,7 +1299,6 @@ class TestBadNextRunAtRecovery:
|
||||
from datetime import timezone, timedelta as td
|
||||
now = datetime.now(UTC)
|
||||
past = (now - td(seconds=30)).isoformat()
|
||||
future = (now + td(days=1)).isoformat()
|
||||
|
||||
# Bad record: next_run_at is not a valid ISO string (e.g. from hand-edit or corruption)
|
||||
# Healthy sibling is past due with good schedule.
|
||||
|
||||
@@ -155,7 +155,7 @@ def test_merge_does_not_mutate_caller_list(hermes_env):
|
||||
not grow as a side effect of save_jobs()."""
|
||||
from cron.jobs import create_job, save_jobs
|
||||
|
||||
job = create_job(
|
||||
create_job(
|
||||
prompt=None,
|
||||
schedule="every 2m",
|
||||
script="watch.sh",
|
||||
|
||||
@@ -105,7 +105,7 @@ class TestPersistedStaleErrorRecovery:
|
||||
|
||||
job = J.get_job(job_id)
|
||||
with mock.patch("cron.jobs.load_jobs", return_value=[job]):
|
||||
n = S.tick(verbose=False, sync=True)
|
||||
S.tick(verbose=False, sync=True)
|
||||
|
||||
latest = E.latest_execution(job_id)
|
||||
assert latest is not None, (
|
||||
|
||||
@@ -100,7 +100,7 @@ class TestStaleInflightSelfHeal:
|
||||
# and the dispatch loop re-fires it.
|
||||
job = J.get_job(job_id)
|
||||
with mock.patch("cron.jobs.load_jobs", return_value=[job]):
|
||||
n = S.tick(verbose=False, sync=True)
|
||||
S.tick(verbose=False, sync=True)
|
||||
|
||||
latest = E.latest_execution(job_id)
|
||||
assert job_id not in S.get_running_job_ids(), "stale claim must be released"
|
||||
|
||||
@@ -465,7 +465,7 @@ def _serve(spool: Path) -> None: # pragma: no cover - runs in the child process
|
||||
async def main() -> None:
|
||||
nonlocal runner
|
||||
parent = int(os.environ.get("C12_PARENT_PID") or os.getppid())
|
||||
watchdog = asyncio.ensure_future(orphan_watchdog(parent))
|
||||
watchdog = asyncio.ensure_future(orphan_watchdog(parent)) # noqa: F841 -- strong ref: the loop holds tasks weakly
|
||||
runner = GatewayRunner(config)
|
||||
ok = await runner.start()
|
||||
if not ok:
|
||||
|
||||
@@ -70,7 +70,7 @@ def test_trace_multisegment_draft_flow():
|
||||
)
|
||||
return r3
|
||||
|
||||
r3 = loop.run_until_complete(turn())
|
||||
loop.run_until_complete(turn())
|
||||
print("\n--- OP TIMELINE ---")
|
||||
for i, op in enumerate(t.ops):
|
||||
print(f"{i:2d} {op['op']:<16} final={op.get('final')} draft_id={op.get('draft_id')} "
|
||||
|
||||
@@ -80,7 +80,6 @@ def _adapter():
|
||||
async def _open_turn_draft(a, chat_id="D01", draft_id=7):
|
||||
"""Open a live draft the way a streaming turn does."""
|
||||
await a.send_draft(chat_id, draft_id, "streaming partial…")
|
||||
key = a._draft_key(chat_id, None)
|
||||
candidates = [k for k in a._open_draft_by_chat if k.startswith(f"{chat_id}:")]
|
||||
assert candidates, "test setup: draft did not arm interception"
|
||||
return candidates[0]
|
||||
|
||||
@@ -527,8 +527,6 @@ def test_restricted_channels_skipped_during_subscribe():
|
||||
adapter._channel_state[CHANNEL] = {"chat_type": "group", "last_ts": 0, "seen": {}}
|
||||
adapter._restricted_channels.add(CHANNEL)
|
||||
|
||||
subscriptions = {}
|
||||
|
||||
class _CountingWs:
|
||||
sent = []
|
||||
|
||||
|
||||
@@ -224,9 +224,6 @@ class TestIncomingDocumentHandling:
|
||||
content1 = b"First file content"
|
||||
content2 = b"Second file content"
|
||||
|
||||
call_count = 0
|
||||
responses = [content1, content2]
|
||||
|
||||
def make_session(_responses):
|
||||
idx = 0
|
||||
|
||||
|
||||
@@ -943,7 +943,6 @@ async def test_discord_dm_does_not_backfill(adapter, monkeypatch):
|
||||
adapter.config.extra["history_backfill"] = True
|
||||
adapter._fetch_channel_context = AsyncMock(return_value="[Recent channel messages]\n[Alice] context")
|
||||
|
||||
bot_user = adapter._client.user
|
||||
dm_channel = SimpleNamespace(
|
||||
id=999,
|
||||
name=None,
|
||||
|
||||
@@ -221,7 +221,7 @@ class TestConfigMigration:
|
||||
import hermes_cli.config as cfg_mod
|
||||
importlib.reload(cfg_mod)
|
||||
|
||||
result = cfg_mod.migrate_config(interactive=False, quiet=True)
|
||||
cfg_mod.migrate_config(interactive=False, quiet=True)
|
||||
# Re-read config
|
||||
updated = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
platforms = updated.get("display", {}).get("platforms", {})
|
||||
|
||||
@@ -171,7 +171,6 @@ class TestFeishuAdapterMessaging(unittest.TestCase):
|
||||
from plugins.platforms.feishu.adapter import FeishuAdapter
|
||||
|
||||
adapter = FeishuAdapter(PlatformConfig())
|
||||
ws_client = SimpleNamespace()
|
||||
owned_executor = object()
|
||||
submitted_executors = []
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@ class TestIRCAdapterMessageParsing:
|
||||
|
||||
# Mock handle_message to capture the event
|
||||
dispatched = []
|
||||
original_dispatch = adapter._dispatch_message
|
||||
|
||||
async def capture_dispatch(**kwargs):
|
||||
dispatched.append(kwargs)
|
||||
|
||||
@@ -475,7 +475,7 @@ class TestCodeExpiry:
|
||||
def test_expired_codes_cleaned_up(self, tmp_path):
|
||||
with patch("gateway.pairing.PAIRING_DIR", tmp_path):
|
||||
store = PairingStore()
|
||||
code = store.generate_code("telegram", "user1")
|
||||
store.generate_code("telegram", "user1")
|
||||
|
||||
# Manually expire all pending entries
|
||||
pending = store._load_json(store._pending_path("telegram"))
|
||||
@@ -535,15 +535,12 @@ class TestListAndClear:
|
||||
class TestUnreadablePairingFile:
|
||||
def test_permission_error_logs_warning_and_returns_empty(self, tmp_path, caplog):
|
||||
import logging
|
||||
import builtins
|
||||
|
||||
approved_path = tmp_path / "weixin-approved.json"
|
||||
approved_path.write_text(
|
||||
'{"o9cq80fake@im.wechat": {"user_name": "x", "approved_at": 0}}'
|
||||
)
|
||||
|
||||
real_open = builtins.open
|
||||
|
||||
def fake_read_text(self, *a, **kw):
|
||||
# Path.read_text uses Path.open internally; raise PermissionError
|
||||
# to mimic a 0600 file owned by a different uid.
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_no_pending_approval_does_not_consume_conversational_yes():
|
||||
session_key = runner._session_key_for_source(source)
|
||||
# No approval registered.
|
||||
|
||||
handled = asyncio.run(
|
||||
asyncio.run(
|
||||
runner._handle_active_session_busy_message(_make_event("yes"), session_key)
|
||||
)
|
||||
|
||||
|
||||
@@ -337,7 +337,6 @@ from plugins.platforms.mattermost.adapter import MattermostAdapter
|
||||
class TestMattermostMultiImage:
|
||||
@pytest.fixture
|
||||
def adapter(self):
|
||||
config = PlatformConfig(enabled=True, token="fake")
|
||||
# Minimal construction via object.__new__ to avoid full setup
|
||||
a = object.__new__(MattermostAdapter)
|
||||
a._base_url = "https://mm.example.com"
|
||||
|
||||
@@ -240,7 +240,7 @@ class TestSendWithRetryRateLimited:
|
||||
adapter = _StubAdapter()
|
||||
flood = SendResult(success=False, error="flood control exceeded, retry in 60 seconds")
|
||||
adapter._send_results = [flood, flood, flood]
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock):
|
||||
result = await adapter._send_with_retry("chat1", "hello", max_retries=2, base_delay=0)
|
||||
assert not result.success
|
||||
# 1 initial + 2 retries = 3 sends; NO delivery-failure notice (4th send)
|
||||
@@ -277,7 +277,7 @@ class TestSendWithRetryFailureTypeTransitions:
|
||||
SendResult(success=False, error="Bad Request: can't parse entities"),
|
||||
# fallback send (auto-succeeds via _next_result)
|
||||
]
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
||||
with patch("asyncio.sleep", new_callable=AsyncMock):
|
||||
result = await adapter._send_with_retry("chat1", "**bold**", max_retries=3, base_delay=0)
|
||||
# The formatting error was not retried further: exactly 1 retry then the
|
||||
# plain-text fallback. 1 initial + 1 retry + 1 fallback = 3 sends.
|
||||
|
||||
@@ -86,7 +86,6 @@ async def test_sentinel_placed_before_agent_setup():
|
||||
concurrent message sees the session as occupied."""
|
||||
runner = _make_runner()
|
||||
event = _make_event()
|
||||
session_key = build_session_key(event.source)
|
||||
|
||||
# Patch _handle_message_with_agent to capture state at entry
|
||||
sentinel_was_set = False
|
||||
|
||||
@@ -4292,7 +4292,6 @@ class TestTrackingStructureBounds:
|
||||
"team_id": "T1",
|
||||
"response_url": f"https://hooks.slack.com/commands/{i}",
|
||||
}
|
||||
respond = AsyncMock()
|
||||
await adapter._handle_slash_command(command)
|
||||
assert len(adapter._slash_command_contexts) <= adapter._SLASH_CTX_MAX
|
||||
# Newest stash survives. Keys are workspace-scoped 3-tuples (#20583)
|
||||
|
||||
@@ -53,7 +53,6 @@ class TestPolicyFromExtra:
|
||||
# Admin lists are scope-specific. DM admin must not auto-promote in groups.
|
||||
extra = {"allow_admin_from": ["111"]}
|
||||
dm = policy_from_extra(extra, "dm")
|
||||
gp = policy_from_extra(extra, "group")
|
||||
assert dm.is_admin("111") is True
|
||||
# Group has no admin list set → gating disabled in groups → "111"
|
||||
# gets unrestricted access, but that's the backward-compat fallback,
|
||||
|
||||
@@ -139,7 +139,7 @@ class TestFeishuFallbackThreadRouting:
|
||||
|
||||
# Call _send_raw_message with reply_to=None and thread_id in metadata
|
||||
import json
|
||||
result = await FeishuAdapter._send_raw_message(
|
||||
await FeishuAdapter._send_raw_message(
|
||||
adapter,
|
||||
chat_id="oc_main_chat",
|
||||
msg_type="text",
|
||||
|
||||
@@ -71,7 +71,6 @@ class TestHandleUpdateCommand:
|
||||
# Since Path(__file__) resolves to the real gateway/run.py,
|
||||
# project_root will be the real hermes-agent dir (which HAS .git).
|
||||
# Patch Path to control this.
|
||||
original_path = Path
|
||||
|
||||
class FakePath(type(Path())):
|
||||
pass
|
||||
@@ -141,7 +140,7 @@ class TestHandleUpdateCommand:
|
||||
patch("hermes_cli.config.detect_install_method", return_value="git"), \
|
||||
patch("shutil.which", side_effect=lambda x: "/usr/bin/hermes" if x == "hermes" else "/usr/bin/setsid"), \
|
||||
patch("subprocess.Popen"):
|
||||
result = await runner._handle_update_command(event)
|
||||
await runner._handle_update_command(event)
|
||||
|
||||
pending_path = hermes_home / ".update_pending.json"
|
||||
assert pending_path.exists()
|
||||
|
||||
@@ -189,7 +189,6 @@ class TestSendText:
|
||||
# At least 2 POST calls
|
||||
assert adapter._http_client.post.call_count >= 2
|
||||
# Second call should NOT have context (only first chunk gets reply_to)
|
||||
first_call = adapter._http_client.post.call_args_list[0]
|
||||
second_call = adapter._http_client.post.call_args_list[1]
|
||||
# No reply_to passed → no context anywhere, but verify structure anyway
|
||||
assert "context" not in second_call.kwargs["json"]
|
||||
|
||||
@@ -145,10 +145,6 @@ class TestWhatsAppCloudAdapterUsesSecretScope:
|
||||
try:
|
||||
from gateway.platforms.whatsapp_cloud import WhatsAppCloudAdapter
|
||||
|
||||
cfg = type("C", (), {
|
||||
"extra": {},
|
||||
"enabled": True,
|
||||
})()
|
||||
# Cloud adapter won't fully init without creds, but we can at least
|
||||
# verify the dm_policy assignment path doesn't crash under scope.
|
||||
# We test via the mixin's behavior instead.
|
||||
|
||||
@@ -205,8 +205,6 @@ class TestP0ChatLockEviction:
|
||||
adapter._outbound.sender._chat_locks[f"chat_{i}"] = asyncio.Lock()
|
||||
|
||||
# Lock the oldest entry
|
||||
oldest_key = next(iter(adapter._outbound.sender._chat_locks))
|
||||
oldest_lock = adapter._outbound.sender._chat_locks[oldest_key]
|
||||
# Simulate a held lock by acquiring it in a non-async way (set _locked)
|
||||
# asyncio.Lock is not held until actually acquired; so we test the
|
||||
# method logic by acquiring the first lock manually.
|
||||
|
||||
@@ -50,7 +50,7 @@ async def test_do_reconnect_calls_set_active_on_success():
|
||||
mock_ws.close = AsyncMock()
|
||||
|
||||
with (
|
||||
patch.object(cm, "_cleanup_ws", new_callable=AsyncMock) as mock_cleanup,
|
||||
patch.object(cm, "_cleanup_ws", new_callable=AsyncMock),
|
||||
patch(
|
||||
"gateway.platforms.yuanbao.SignManager.force_refresh",
|
||||
new_callable=AsyncMock,
|
||||
|
||||
@@ -33,7 +33,6 @@ class TestCLISubagentInterrupt(unittest.TestCase):
|
||||
|
||||
interrupt_detected = threading.Event()
|
||||
child_started = threading.Event()
|
||||
child_api_call_count = 0
|
||||
|
||||
# Create a real-enough parent agent
|
||||
parent = AIAgent.__new__(AIAgent)
|
||||
@@ -63,7 +62,6 @@ class TestCLISubagentInterrupt(unittest.TestCase):
|
||||
parent._execution_thread_id = None
|
||||
|
||||
# We'll track what happens with _active_children
|
||||
original_children = parent._active_children
|
||||
|
||||
# Mock the child's run_conversation to simulate a slow operation
|
||||
# that checks _interrupt_requested like the real one does
|
||||
|
||||
@@ -743,7 +743,6 @@ class TestLaunchdServiceRecovery:
|
||||
# _launchd_domain() must return user/<uid>.
|
||||
gateway_cli._resolved_launchd_domain = None
|
||||
monkeypatch.setattr(os, "getuid", lambda: 501)
|
||||
label = gateway_cli.get_launchd_label()
|
||||
|
||||
def fake_run(cmd, check=False, **kwargs):
|
||||
if "print" in cmd and "gui/" in " ".join(cmd):
|
||||
@@ -1608,7 +1607,6 @@ class TestSystemServiceIdentityRootHandling:
|
||||
"""When root is explicitly passed via --run-as-user root, allow it."""
|
||||
|
||||
root_info = pwd.getpwnam("root")
|
||||
root_group = grp.getgrgid(root_info.pw_gid).gr_name
|
||||
|
||||
username, _group, home, _uid = gateway_cli._system_service_identity(run_as_user="root")
|
||||
assert username == "root"
|
||||
|
||||
@@ -100,7 +100,7 @@ class TestSessionActiveDirectory:
|
||||
monkeypatch.chdir(launch_dir)
|
||||
|
||||
record_session_cwd("gateway:999", str(workspace))
|
||||
tokens = set_session_vars(session_key="gateway:999")
|
||||
set_session_vars(session_key="gateway:999")
|
||||
try:
|
||||
prompt = build_init_prompt_for_cwd()
|
||||
assert f"for the project at: {workspace}" in prompt
|
||||
|
||||
@@ -177,7 +177,7 @@ def test_dependency_block_with_open_parent_stays_parked_across_dispatch_tick(
|
||||
with kb.write_txn(conn):
|
||||
conn.execute("UPDATE tasks SET status='running' WHERE id=?", (child,))
|
||||
assert kb.block_task(conn, child, reason="wait", kind="dependency")
|
||||
res = kbd.dispatch_once(conn, spawn_fn=fake_spawn)
|
||||
kbd.dispatch_once(conn, spawn_fn=fake_spawn)
|
||||
assert kb.get_task(conn, child).status == "todo"
|
||||
assert child not in spawns
|
||||
with kb.write_txn(conn):
|
||||
|
||||
@@ -72,7 +72,7 @@ def test_capped_tasks_dispatched_on_subsequent_tick(isolated_kanban_home_with_pr
|
||||
from hermes_cli import kanban_db_dispatch as kbd
|
||||
with kbc.connect_closing() as conn:
|
||||
kb.create_board(slug="default", name="Test")
|
||||
ids = [kb.create_task(conn, title=f"a{i}", assignee="alpha") for i in range(3)]
|
||||
[kb.create_task(conn, title=f"a{i}", assignee="alpha") for i in range(3)]
|
||||
|
||||
# First tick: cap=1, only 1 alpha dispatched
|
||||
with kbc.connect_closing() as conn:
|
||||
|
||||
@@ -56,7 +56,6 @@ def test_create_swarm_graph_is_atomic_and_rolls_back_partial_build(
|
||||
writer = kbc.connect(db_path)
|
||||
reader = kbc.connect(db_path)
|
||||
original_create = kb.create_task
|
||||
original_complete = kb.complete_task
|
||||
calls = 0
|
||||
|
||||
def observed_create(*args, **kwargs):
|
||||
|
||||
@@ -118,10 +118,6 @@ def test_reap_orphans_kills_only_our_parentless_binaries(tmp_path, monkeypatch):
|
||||
exe = tmp_path / "llama-server.exe"
|
||||
exe.write_text("")
|
||||
|
||||
orphan = _FakeChild(300)
|
||||
adopted = _FakeChild(301) # parent alive -> not an orphan
|
||||
foreign = _FakeChild(302) # different binary -> never touched
|
||||
|
||||
def _info(pid, exe_path, ppid):
|
||||
p = _FakeChild(pid)
|
||||
p.info = {"exe": exe_path, "ppid": ppid}
|
||||
|
||||
@@ -117,7 +117,7 @@ class TestGatewayPersonalityNone:
|
||||
p1, p2 = self._gateway_env(tmp_path)
|
||||
with p1, p2:
|
||||
event = self._make_event("default")
|
||||
result = await runner._handle_personality_command(event)
|
||||
await runner._handle_personality_command(event)
|
||||
|
||||
saved = yaml.safe_load(config_file.read_text())
|
||||
assert saved["agent"]["system_prompt"] == "manual forever"
|
||||
|
||||
@@ -570,11 +570,11 @@ def test_install_refuses_a_non_https_update_url(monkeypatch, tmp_path, update_ur
|
||||
def test_install_saves_an_https_update_url_tag(monkeypatch, tmp_path):
|
||||
from hermes_cli.plugins_cmd import _install_plugin_core
|
||||
|
||||
repo, _old, sha = _plugin_repo(tmp_path)
|
||||
repo, _old, _sha = _plugin_repo(tmp_path)
|
||||
(repo / "plugin.yaml").write_text(
|
||||
yaml.safe_dump({"name": "demo", "version": "1.0.0", "update_url": " https://feed.example/f.yml "}),
|
||||
encoding="utf-8")
|
||||
sha = _commit(repo, "feed", "feed")
|
||||
_commit(repo, "feed", "feed")
|
||||
home = tmp_path / "home"
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
|
||||
|
||||
@@ -1070,7 +1070,6 @@ def test_unload_cancels_a_deferred_platform_before_module_load():
|
||||
scope=scope,
|
||||
)
|
||||
platform_registry.register_deferred(name, old_loader, scope=scope)
|
||||
displaced = platform_registry.snapshot_registration(name, scope=scope)
|
||||
manager = PluginManager(scope_key=scope)
|
||||
manifest = PluginManifest(
|
||||
name=f"{name}-platform",
|
||||
|
||||
@@ -83,7 +83,7 @@ def test_cli_export_rejects_bad_profile_name_without_traceback(
|
||||
tmp_path, monkeypatch, capsys, profiles
|
||||
):
|
||||
"""A bad name must print a clean error — the helper raises before export."""
|
||||
main_mod = importlib.import_module("hermes_cli.main")
|
||||
importlib.import_module("hermes_cli.main")
|
||||
default_home = tmp_path / ".hermes"
|
||||
default_home.mkdir()
|
||||
monkeypatch.setattr(profiles, "_get_default_hermes_home", lambda: default_home)
|
||||
@@ -104,7 +104,7 @@ def test_cli_export_rejects_bad_profile_name_without_traceback(
|
||||
def test_cli_export_default_does_not_write_into_the_current_checkout(
|
||||
tmp_path, monkeypatch, capsys, profiles
|
||||
):
|
||||
main_mod = importlib.import_module("hermes_cli.main")
|
||||
importlib.import_module("hermes_cli.main")
|
||||
default_home = tmp_path / ".hermes"
|
||||
default_home.mkdir()
|
||||
(default_home / "config.yaml").write_text("model: test\n", encoding="utf-8")
|
||||
|
||||
@@ -1274,7 +1274,6 @@ class TestRenameProfile:
|
||||
def test_rename_delegates_identity_migration_to_live_gateway(self, profile_env):
|
||||
"""Under a live multiplexer the CLI must NOT rewrite the routing DB directly (the gateway holds
|
||||
it in memory and would clobber the write); it delegates to the control verb instead."""
|
||||
tmp_path = profile_env
|
||||
create_profile("oldname", no_alias=True)
|
||||
|
||||
with patch("hermes_cli.profiles.check_alias_collision", return_value="skip"), \
|
||||
@@ -1658,7 +1657,6 @@ class TestEdgeCases:
|
||||
|
||||
def test_clone_from_named_profile(self, profile_env):
|
||||
"""Clone config from a named (non-default) profile."""
|
||||
tmp_path = profile_env
|
||||
# Create source profile with config
|
||||
source_dir = create_profile("source", no_alias=True)
|
||||
(source_dir / "config.yaml").write_text("model: cloned", encoding="utf-8")
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestDoUninstallSkipConfirm:
|
||||
def test_skip_confirm_bypasses_input(self):
|
||||
"""With skip_confirm=True, input() should not be called."""
|
||||
from hermes_cli.skills_hub import do_uninstall
|
||||
with patch("hermes_cli.skills_hub._console") as mock_console, \
|
||||
with patch("hermes_cli.skills_hub._console"), \
|
||||
patch("tools.skills_hub_install.uninstall_skill", return_value=(True, "Removed")) as mock_uninstall, \
|
||||
patch("builtins.input") as mock_input:
|
||||
do_uninstall("test-skill", skip_confirm=True)
|
||||
|
||||
@@ -576,7 +576,7 @@ class TestRestartMacosLaunchdGateways:
|
||||
assert restarted == ["ai.hermes.gateway", "ai.hermes.gateway-after"]
|
||||
|
||||
def test_sibling_that_never_comes_back_is_failed(self, monkeypatch, tmp_path):
|
||||
rec = _fleet(
|
||||
_fleet(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
current="ai.hermes.gateway",
|
||||
|
||||
@@ -136,7 +136,7 @@ class TestUpdateStatusReadsReceipt:
|
||||
def test_running_receipt_proves_nothing(self, client, tmp_path, monkeypatch):
|
||||
"""An unfinished receipt (crashed run / mid-update) must not report
|
||||
an outcome — clients keep polling."""
|
||||
receipt = _write_receipt(tmp_path, monkeypatch, outcome="running")
|
||||
_write_receipt(tmp_path, monkeypatch, outcome="running")
|
||||
self._clear_registries(monkeypatch, tmp_path)
|
||||
|
||||
resp = client.get("/api/actions/hermes-update/status")
|
||||
|
||||
@@ -129,7 +129,6 @@ def test_get_status_does_not_block_event_loop():
|
||||
import httpx
|
||||
|
||||
results: dict[str, float] = {}
|
||||
errors: list[str] = []
|
||||
|
||||
async def _run():
|
||||
transport = httpx.ASGITransport(app=web_server_mod.app)
|
||||
|
||||
@@ -416,7 +416,7 @@ class TestConnectionLifecycle:
|
||||
read_only = SessionDB(db_path=db_path, read_only=True)
|
||||
try:
|
||||
assert read_only._fts_enabled is True
|
||||
matches = read_only.search_messages("wal-race")
|
||||
read_only.search_messages("wal-race")
|
||||
finally:
|
||||
read_only.close()
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ def test_read_conn_open_failure_marks_thread(db, monkeypatch, tmp_path):
|
||||
def test_anchored_view_and_around_use_read_path(db):
|
||||
msgs = db.get_messages("s1")
|
||||
anchor = msgs[0]["id"]
|
||||
acquired = db._lock.acquire()
|
||||
db._lock.acquire()
|
||||
try:
|
||||
done = {}
|
||||
|
||||
@@ -166,7 +166,7 @@ def test_session_resume_reads_do_not_take_writer_lock(db):
|
||||
db.append_message("child1", role="user", content="child turn")
|
||||
db.append_message("child1", role="assistant", content="child reply")
|
||||
|
||||
acquired = db._lock.acquire()
|
||||
db._lock.acquire()
|
||||
try:
|
||||
done = {}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class TestSlashGateIsolation:
|
||||
def test_evaluate_slash_channel_gate_per_adapter(self, monkeypatch):
|
||||
import types
|
||||
|
||||
discord_lib = pytest.importorskip(
|
||||
pytest.importorskip(
|
||||
"discord", reason="discord.py optional dep not installed"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ def test_missing_bundle_tool_is_installed_in_writable_store(pm_env, tmp_path, mo
|
||||
from pm.install import ensure, env_for, is_installed
|
||||
import importlib
|
||||
|
||||
fixture = pm_env
|
||||
shipped = tmp_path / "payload" / "tools"
|
||||
shipped.mkdir(parents=True)
|
||||
(shipped.parent / "manifest.json").write_text('{"repo":"core","store":"tools","venv":"venv"}')
|
||||
|
||||
@@ -205,7 +205,7 @@ class TestModelPickerBaseUrlIntegration:
|
||||
return_value={"api_key": "sk-test", "base_url": "https://custom.proxy.com"}),
|
||||
):
|
||||
from hermes_cli.models import provider_model_ids
|
||||
result = provider_model_ids("test-provider")
|
||||
provider_model_ids("test-provider")
|
||||
# Verify fetch_models was called with base_url
|
||||
mock_profile.fetch_models.assert_called_once()
|
||||
call_kwargs = mock_profile.fetch_models.call_args
|
||||
|
||||
@@ -433,7 +433,7 @@ def test_slack_settings_migrated(tmp_path: Path):
|
||||
workspace_target=None, overwrite=False, migrate_secrets=False, output_dir=None,
|
||||
selected_options={"slack-settings"},
|
||||
)
|
||||
report = migrator.migrate()
|
||||
migrator.migrate()
|
||||
env_text = (target / ".env").read_text(encoding="utf-8")
|
||||
assert "SLACK_BOT_TOKEN=xoxb-slack-bot" in env_text
|
||||
assert "SLACK_APP_TOKEN=xapp-slack-app" in env_text
|
||||
@@ -464,7 +464,7 @@ def test_model_config_migrated(tmp_path: Path):
|
||||
workspace_target=None, overwrite=True, migrate_secrets=False, output_dir=None,
|
||||
selected_options={"model-config"},
|
||||
)
|
||||
report = migrator.migrate()
|
||||
migrator.migrate()
|
||||
config_text = (target / "config.yaml").read_text(encoding="utf-8")
|
||||
assert "anthropic/claude-sonnet-4" in config_text
|
||||
|
||||
@@ -492,7 +492,7 @@ def test_shared_skills_migrated(tmp_path: Path):
|
||||
workspace_target=None, overwrite=False, migrate_secrets=False, output_dir=None,
|
||||
selected_options={"shared-skills"},
|
||||
)
|
||||
report = migrator.migrate()
|
||||
migrator.migrate()
|
||||
imported = target / "skills" / mod.SKILL_CATEGORY_DIRNAME / "my-shared-skill" / "SKILL.md"
|
||||
assert imported.exists()
|
||||
|
||||
@@ -520,7 +520,7 @@ def test_daily_memory_merged(tmp_path: Path):
|
||||
workspace_target=None, overwrite=False, migrate_secrets=False, output_dir=None,
|
||||
selected_options={"daily-memory"},
|
||||
)
|
||||
report = migrator.migrate()
|
||||
migrator.migrate()
|
||||
mem_path = target / "memories" / "MEMORY.md"
|
||||
assert mem_path.exists()
|
||||
content = mem_path.read_text(encoding="utf-8")
|
||||
@@ -556,7 +556,7 @@ def test_provider_keys_require_migrate_secrets_flag(tmp_path: Path):
|
||||
workspace_target=None, overwrite=False, migrate_secrets=False, output_dir=None,
|
||||
selected_options={"provider-keys"},
|
||||
)
|
||||
report = migrator.migrate()
|
||||
migrator.migrate()
|
||||
env_path = target / ".env"
|
||||
if env_path.exists():
|
||||
assert "sk-or-test-key" not in env_path.read_text(encoding="utf-8")
|
||||
@@ -567,7 +567,7 @@ def test_provider_keys_require_migrate_secrets_flag(tmp_path: Path):
|
||||
workspace_target=None, overwrite=False, migrate_secrets=True, output_dir=None,
|
||||
selected_options={"provider-keys"},
|
||||
)
|
||||
report2 = migrator2.migrate()
|
||||
migrator2.migrate()
|
||||
env_text = (target / ".env").read_text(encoding="utf-8")
|
||||
assert "OPENROUTER_API_KEY=sk-or-test-key" in env_text
|
||||
|
||||
|
||||
@@ -460,7 +460,6 @@ class TestSetupLogging:
|
||||
|
||||
hermes_logging.setup_logging(hermes_home=hermes_home, log_level="WARNING")
|
||||
|
||||
root = logging.getLogger()
|
||||
agent_handlers = [
|
||||
h for h in hermes_logging._queued_file_handlers
|
||||
if isinstance(h, RotatingFileHandler)
|
||||
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user