refactor(cleanup): remove legacy CLI and complete EvoAgent migration cleanup
- Delete backend/cli.py and all CLI-specific tests (test_cli.py, test_openclaw_cli_service.py, test_skills_cli.py) - Remove evotraders console script from pyproject.toml - Update README/CLAUDE.md to reference python backend/main.py instead of CLI - Add pytest-asyncio to dev dependencies - Enhance EvoAgent with reload_runtime_assets and backward-compat attrs - Align tests with updated API semantics and gateway process models Constraint: CLI is deprecated in favor of split-service runtime model Confidence: high Scope-risk: moderate
This commit is contained in:
@@ -38,7 +38,7 @@ def test_agent_service_status_includes_scope_metadata(tmp_path):
|
||||
payload = response.json()
|
||||
assert payload["scope"]["design_time_registry"]["root"] == str(tmp_path / "workspaces")
|
||||
assert payload["scope"]["runtime_assets"]["root"] == str(tmp_path / "runs")
|
||||
assert "runs/<run_id>" in payload["scope"]["agent_route_note"]
|
||||
assert "runs/{run_id}" in payload["scope"]["agent_route_note"]
|
||||
|
||||
|
||||
def test_agent_service_read_routes(monkeypatch, tmp_path):
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from backend import cli
|
||||
|
||||
|
||||
def test_live_runs_incremental_market_store_update_before_start(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
(project_root / ".env").write_text("FINNHUB_API_KEY=test\n", encoding="utf-8")
|
||||
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: calls.append(("run_data_updater", project_root)))
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_update_market_store",
|
||||
lambda config_name, end_date=None: calls.append(("auto_update_market_store", config_name, end_date)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: calls.append(
|
||||
("auto_enrich_market_store", config_name, end_date, lookback_days, force)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: calls.append(("chdir", Path(path))))
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(("subprocess.run", cmd, check))
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
cli.live(
|
||||
config_name="smoke_fullstack",
|
||||
host="0.0.0.0",
|
||||
port=8765,
|
||||
trigger_time="now",
|
||||
poll_interval=10,
|
||||
clean=False,
|
||||
enable_memory=False,
|
||||
)
|
||||
|
||||
assert any(item[0] == "run_data_updater" for item in calls)
|
||||
assert any(
|
||||
item[0] == "auto_update_market_store" and item[1] == "smoke_fullstack"
|
||||
for item in calls
|
||||
)
|
||||
assert any(
|
||||
item[0] == "auto_enrich_market_store" and item[1] == "smoke_fullstack"
|
||||
for item in calls
|
||||
)
|
||||
run_call = next(item for item in calls if item[0] == "subprocess.run")
|
||||
assert run_call[1][:6] == [
|
||||
cli.sys.executable,
|
||||
"-u",
|
||||
"-m",
|
||||
"backend.main",
|
||||
"--mode",
|
||||
"live",
|
||||
]
|
||||
|
||||
|
||||
def test_backtest_runs_full_market_store_prepare_before_start(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: calls.append(("run_data_updater", project_root)))
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_prepare_backtest_market_store",
|
||||
lambda config_name, start_date, end_date: calls.append(
|
||||
("auto_prepare_backtest_market_store", config_name, start_date, end_date)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: calls.append(
|
||||
("auto_enrich_market_store", config_name, end_date, lookback_days, force)
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: calls.append(("chdir", Path(path))))
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(("subprocess.run", cmd, check))
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
cli.backtest(
|
||||
start="2026-03-01",
|
||||
end="2026-03-10",
|
||||
config_name="smoke_fullstack",
|
||||
host="0.0.0.0",
|
||||
port=8765,
|
||||
poll_interval=10,
|
||||
clean=False,
|
||||
enable_memory=False,
|
||||
)
|
||||
|
||||
assert any(item[0] == "run_data_updater" for item in calls)
|
||||
assert any(
|
||||
item[0] == "auto_prepare_backtest_market_store"
|
||||
and item[1:] == ("smoke_fullstack", "2026-03-01", "2026-03-10")
|
||||
for item in calls
|
||||
)
|
||||
assert any(
|
||||
item[0] == "auto_enrich_market_store"
|
||||
and item[1] == "smoke_fullstack"
|
||||
and item[2] == "2026-03-10"
|
||||
for item in calls
|
||||
)
|
||||
run_call = next(item for item in calls if item[0] == "subprocess.run")
|
||||
assert run_call[1][:6] == [
|
||||
cli.sys.executable,
|
||||
"-u",
|
||||
"-m",
|
||||
"backend.main",
|
||||
"--mode",
|
||||
"backtest",
|
||||
]
|
||||
|
||||
|
||||
def test_live_cli_defaults_to_generic_run_label(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
(project_root / ".env").write_text("FINNHUB_API_KEY=test\n", encoding="utf-8")
|
||||
|
||||
calls = []
|
||||
runner = CliRunner()
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: None)
|
||||
monkeypatch.setattr(cli, "auto_update_market_store", lambda config_name, end_date=None: None)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: None,
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: None)
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(cmd)
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
result = runner.invoke(cli.app, ["live", "--trigger-time", "now"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert calls
|
||||
assert "--config-name" in calls[0]
|
||||
config_index = calls[0].index("--config-name")
|
||||
assert calls[0][config_index + 1] == "default_live_run"
|
||||
|
||||
|
||||
def test_backtest_cli_defaults_to_generic_run_label(monkeypatch, tmp_path):
|
||||
project_root = tmp_path
|
||||
calls = []
|
||||
runner = CliRunner()
|
||||
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: project_root)
|
||||
monkeypatch.setattr(cli, "handle_history_cleanup", lambda config_name, auto_clean=False: None)
|
||||
monkeypatch.setattr(cli, "run_data_updater", lambda project_root: None)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_prepare_backtest_market_store",
|
||||
lambda config_name, start_date, end_date: None,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"auto_enrich_market_store",
|
||||
lambda config_name, end_date=None, lookback_days=120, force=False: None,
|
||||
)
|
||||
monkeypatch.setattr(cli.os, "chdir", lambda path: None)
|
||||
|
||||
def fake_run(cmd, check=True, **kwargs):
|
||||
calls.append(cmd)
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
||||
|
||||
result = runner.invoke(
|
||||
cli.app,
|
||||
["backtest", "--start", "2026-03-01", "--end", "2026-03-10"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert calls
|
||||
assert "--config-name" in calls[0]
|
||||
config_index = calls[0].index("--config-name")
|
||||
assert calls[0][config_index + 1] == "default_backtest_run"
|
||||
|
||||
|
||||
def test_main_parser_defaults_to_generic_run_label():
|
||||
from backend.main import build_arg_parser
|
||||
|
||||
parser = build_arg_parser()
|
||||
args = parser.parse_args([])
|
||||
|
||||
assert args.config_name == "default_run"
|
||||
|
||||
|
||||
def test_ingest_enrich_runs_batch_enrichment(monkeypatch):
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(cli, "_resolve_symbols", lambda raw_tickers, config_name=None: ["AAPL", "MSFT"])
|
||||
|
||||
class DummyStore:
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(cli, "MarketStore", lambda: DummyStore())
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"enrich_symbols",
|
||||
lambda store, symbols, start_date=None, end_date=None, limit=200, analysis_source="local", skip_existing=True: calls.append(
|
||||
("enrich_symbols", symbols, start_date, end_date, limit, analysis_source, skip_existing)
|
||||
) or [
|
||||
{
|
||||
"symbol": symbol,
|
||||
"news_count": 3,
|
||||
"queued_count": 3,
|
||||
"analyzed": 3,
|
||||
"skipped_existing_count": 0,
|
||||
"deduped_count": 0,
|
||||
"llm_count": 0,
|
||||
"local_count": 3,
|
||||
}
|
||||
for symbol in symbols
|
||||
],
|
||||
)
|
||||
|
||||
cli.ingest_enrich(
|
||||
tickers=None,
|
||||
start="2026-03-01",
|
||||
end="2026-03-10",
|
||||
limit=150,
|
||||
force=False,
|
||||
config_name="smoke_fullstack",
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
("enrich_symbols", ["AAPL", "MSFT"], "2026-03-01", "2026-03-10", 150, "local", True)
|
||||
]
|
||||
|
||||
|
||||
def test_ingest_report_reads_market_store_report(monkeypatch):
|
||||
calls = []
|
||||
printed = []
|
||||
|
||||
monkeypatch.setattr(cli, "_resolve_symbols", lambda raw_tickers, config_name=None: ["AAPL"])
|
||||
|
||||
class DummyStore:
|
||||
def get_enrich_report(self, symbols=None, start_date=None, end_date=None):
|
||||
calls.append(("get_enrich_report", symbols, start_date, end_date))
|
||||
return [
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"raw_news_count": 10,
|
||||
"analyzed_news_count": 8,
|
||||
"coverage_pct": 80.0,
|
||||
"llm_count": 5,
|
||||
"local_count": 3,
|
||||
"latest_trade_date": "2026-03-16",
|
||||
"latest_analysis_at": "2026-03-16T09:00:00",
|
||||
}
|
||||
]
|
||||
|
||||
monkeypatch.setattr(cli, "MarketStore", lambda: DummyStore())
|
||||
monkeypatch.setattr(cli, "get_explain_model_info", lambda: {"provider": "DASHSCOPE", "model_name": "qwen-max", "label": "DASHSCOPE:qwen-max"})
|
||||
monkeypatch.setattr(cli, "llm_enrichment_enabled", lambda: True)
|
||||
monkeypatch.setattr(cli.console, "print", lambda value: printed.append(value))
|
||||
|
||||
cli.ingest_report(
|
||||
tickers=None,
|
||||
start="2026-03-01",
|
||||
end="2026-03-16",
|
||||
config_name="smoke_fullstack",
|
||||
only_problematic=False,
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
("get_enrich_report", ["AAPL"], "2026-03-01", "2026-03-16")
|
||||
]
|
||||
assert printed
|
||||
assert getattr(printed[0], "caption", "") == "Explain LLM: DASHSCOPE:qwen-max"
|
||||
|
||||
|
||||
def test_filter_problematic_report_rows_keeps_low_coverage_and_no_llm():
|
||||
rows = [
|
||||
{
|
||||
"symbol": "AAPL",
|
||||
"coverage_pct": 100.0,
|
||||
"llm_count": 2,
|
||||
},
|
||||
{
|
||||
"symbol": "MSFT",
|
||||
"coverage_pct": 80.0,
|
||||
"llm_count": 1,
|
||||
},
|
||||
{
|
||||
"symbol": "NVDA",
|
||||
"coverage_pct": 100.0,
|
||||
"llm_count": 0,
|
||||
},
|
||||
]
|
||||
|
||||
filtered = cli._filter_problematic_report_rows(rows)
|
||||
|
||||
assert [row["symbol"] for row in filtered] == ["MSFT", "NVDA"]
|
||||
@@ -198,6 +198,7 @@ def test_evo_agent_reload_runtime_assets_refreshes_prompt_files(monkeypatch, tmp
|
||||
workspace_dir=workspace_dir,
|
||||
model=DummyModel(),
|
||||
formatter=DummyFormatter(),
|
||||
prompt_files=["SOUL.md"],
|
||||
skills_manager=type(
|
||||
"SkillsManagerStub",
|
||||
(),
|
||||
@@ -248,11 +249,15 @@ def test_pipeline_create_runtime_analyst_uses_evo_agent_when_enabled(monkeypatch
|
||||
created = {}
|
||||
|
||||
class DummyEvoAgent:
|
||||
name = "test_analyst"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
created.update(kwargs)
|
||||
self.toolkit = None
|
||||
|
||||
class DummyAnalystAgent:
|
||||
name = "test_analyst"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
created.update(kwargs)
|
||||
self.toolkit = None
|
||||
@@ -308,11 +313,15 @@ def test_pipeline_create_runtime_analyst_uses_legacy_when_not_in_evo_ids(monkeyp
|
||||
created = {}
|
||||
|
||||
class DummyEvoAgent:
|
||||
name = "test_analyst"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
created.update(kwargs)
|
||||
self.toolkit = None
|
||||
|
||||
class DummyAnalystAgent:
|
||||
name = "test_analyst"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
created.update(kwargs)
|
||||
self.toolkit = None
|
||||
|
||||
@@ -115,7 +115,7 @@ class TestPollingPriceManager:
|
||||
{"c": 100.0, "o": 99.0, "h": 101.0, "l": 98.0, "pc": 99.5, "d": 0.5, "dp": 0.5, "t": 1},
|
||||
],
|
||||
):
|
||||
with caplog.at_level(logging.INFO):
|
||||
with caplog.at_level(logging.INFO, logger="backend.data.polling_price_manager"):
|
||||
manager._fetch_prices()
|
||||
manager._fetch_prices()
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for the OpenClaw CLI service wrapper."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.services.openclaw_cli import OpenClawCliError, OpenClawCliService
|
||||
|
||||
|
||||
class _Completed:
|
||||
def __init__(self, *, returncode=0, stdout="", stderr=""):
|
||||
self.returncode = returncode
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
|
||||
|
||||
def test_openclaw_cli_service_runs_json_command(monkeypatch, tmp_path):
|
||||
captured = {}
|
||||
|
||||
def _fake_run(command, **kwargs):
|
||||
captured["command"] = command
|
||||
captured["cwd"] = kwargs["cwd"]
|
||||
return _Completed(stdout='{"sessions":[{"key":"main/session-1"}]}')
|
||||
|
||||
monkeypatch.setattr("backend.services.openclaw_cli.subprocess.run", _fake_run)
|
||||
|
||||
service = OpenClawCliService(base_command=["openclaw"], cwd=tmp_path, timeout_seconds=3)
|
||||
payload = service.list_sessions()
|
||||
|
||||
assert payload["sessions"][0]["key"] == "main/session-1"
|
||||
assert captured["command"] == ["openclaw", "sessions", "--json"]
|
||||
assert captured["cwd"] == tmp_path
|
||||
|
||||
|
||||
def test_openclaw_cli_service_raises_on_failure(monkeypatch, tmp_path):
|
||||
def _fake_run(command, **kwargs):
|
||||
return _Completed(returncode=7, stdout="", stderr="boom")
|
||||
|
||||
monkeypatch.setattr("backend.services.openclaw_cli.subprocess.run", _fake_run)
|
||||
|
||||
service = OpenClawCliService(base_command=["openclaw"], cwd=tmp_path, timeout_seconds=3)
|
||||
|
||||
with pytest.raises(OpenClawCliError) as exc_info:
|
||||
service.list_cron_jobs()
|
||||
|
||||
assert exc_info.value.exit_code == 7
|
||||
assert exc_info.value.stderr == "boom"
|
||||
|
||||
|
||||
def test_openclaw_cli_service_can_extract_single_session(monkeypatch, tmp_path):
|
||||
def _fake_run(command, **kwargs):
|
||||
return _Completed(stdout='{"sessions":[{"key":"main/session-1","agentId":"main"}]}')
|
||||
|
||||
monkeypatch.setattr("backend.services.openclaw_cli.subprocess.run", _fake_run)
|
||||
|
||||
service = OpenClawCliService(base_command=["openclaw"], cwd=tmp_path, timeout_seconds=3)
|
||||
session = service.get_session("main/session-1")
|
||||
|
||||
assert session["agentId"] == "main"
|
||||
@@ -43,11 +43,49 @@ class _FakeOpenClawCliService:
|
||||
"items": [{"role": "assistant", "text": "hello"}],
|
||||
}
|
||||
|
||||
def status_model(self):
|
||||
from shared.models.openclaw import OpenClawStatus
|
||||
return OpenClawStatus(runtimeVersion="2026.3.24")
|
||||
|
||||
def get_session_model(self, session_key: str):
|
||||
from shared.models.openclaw import SessionEntry
|
||||
for session in self.list_sessions()["sessions"]:
|
||||
if session["key"] == session_key:
|
||||
return SessionEntry.model_validate(session, strict=False)
|
||||
raise KeyError(session_key)
|
||||
|
||||
def list_sessions_model(self):
|
||||
from shared.models.openclaw import SessionsList, SessionEntry
|
||||
sessions = [
|
||||
SessionEntry.model_validate(s, strict=False)
|
||||
for s in self.list_sessions()["sessions"]
|
||||
]
|
||||
return SessionsList(sessions=sessions)
|
||||
|
||||
def get_session_history_model(self, session_key: str, *, limit: int = 20):
|
||||
from shared.models.openclaw import SessionHistory
|
||||
raw = self.get_session_history(session_key, limit=limit)
|
||||
return SessionHistory(
|
||||
sessionKey=raw["sessionKey"],
|
||||
session_id=None,
|
||||
events=raw["items"],
|
||||
history=raw["items"],
|
||||
raw_text=None,
|
||||
)
|
||||
|
||||
def list_cron_jobs(self):
|
||||
return {"jobs": [{"id": "job-1", "name": "Daily sync"}]}
|
||||
|
||||
def list_cron_jobs_model(self):
|
||||
from shared.models.openclaw import CronList
|
||||
return CronList.from_raw(self.list_cron_jobs())
|
||||
|
||||
def list_approvals(self):
|
||||
return {"approvals": [{"id": "ap-1", "status": "pending"}]}
|
||||
return {"approvals": [{"approvalId": "ap-1", "toolName": "test_tool", "status": "pending"}]}
|
||||
|
||||
def list_approvals_model(self):
|
||||
from shared.models.openclaw import ApprovalsList
|
||||
return ApprovalsList.from_raw(self.list_approvals())
|
||||
|
||||
|
||||
def test_openclaw_service_routes_are_exposed():
|
||||
@@ -85,17 +123,17 @@ def test_openclaw_service_read_routes():
|
||||
assert status.status_code == 200
|
||||
assert status.json()["status"] == "operational"
|
||||
assert openclaw_status.status_code == 200
|
||||
assert openclaw_status.json()["runtimeVersion"] == "2026.3.24"
|
||||
assert openclaw_status.json()["runtime_version"] == "2026.3.24"
|
||||
assert sessions.status_code == 200
|
||||
assert len(sessions.json()["sessions"]) == 2
|
||||
assert session.status_code == 200
|
||||
assert session.json()["session"]["agentId"] == "main"
|
||||
assert session.json()["session"]["agent_id"] == "main"
|
||||
assert history.status_code == 200
|
||||
assert history.json()["limit"] == 5
|
||||
assert len(history.json()["events"]) == 1
|
||||
assert cron.status_code == 200
|
||||
assert cron.json()["jobs"][0]["id"] == "job-1"
|
||||
assert approvals.status_code == 200
|
||||
assert approvals.json()["approvals"][0]["id"] == "ap-1"
|
||||
assert approvals.json()["approvals"][0]["approval_id"] == "ap-1"
|
||||
|
||||
|
||||
def test_openclaw_service_session_404():
|
||||
|
||||
@@ -38,8 +38,13 @@ def test_runtime_service_health_and_status(monkeypatch):
|
||||
assert health_response.json() == {
|
||||
"status": "healthy",
|
||||
"service": "runtime-service",
|
||||
"gateway_running": False,
|
||||
"gateway_port": 9876,
|
||||
"gateway": {
|
||||
"running": False,
|
||||
"port": 9876,
|
||||
"pid": None,
|
||||
"process_status": "not_running",
|
||||
"returncode": None,
|
||||
},
|
||||
}
|
||||
assert status_response.status_code == 200
|
||||
assert status_response.json() == {
|
||||
@@ -48,6 +53,8 @@ def test_runtime_service_health_and_status(monkeypatch):
|
||||
"runtime": {
|
||||
"gateway_running": False,
|
||||
"gateway_port": 9876,
|
||||
"gateway_pid": None,
|
||||
"gateway_process_status": "not_running",
|
||||
"has_runtime_manager": True,
|
||||
},
|
||||
}
|
||||
@@ -365,6 +372,8 @@ def test_runtime_service_start_stop_lifecycle_contract(monkeypatch, tmp_path):
|
||||
return self.context
|
||||
|
||||
class _DummyProcess:
|
||||
pid = 12345
|
||||
|
||||
def poll(self):
|
||||
return None
|
||||
|
||||
@@ -574,6 +583,8 @@ def test_start_runtime_restore_reuses_historical_run_id(monkeypatch, tmp_path):
|
||||
return self.context
|
||||
|
||||
class _DummyProcess:
|
||||
pid = 12345
|
||||
|
||||
def poll(self):
|
||||
return None
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ class _DummyAsyncClient:
|
||||
|
||||
async def get(self, path, params=None):
|
||||
self.calls.append(("get", path, params))
|
||||
if path == "/sessions/main/session-1":
|
||||
return _DummyResponse({"session": {"key": "main/session-1", "agentId": "main"}})
|
||||
return _DummyResponse({"path": path, "params": params})
|
||||
|
||||
async def post(self, path, json=None):
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from backend import cli
|
||||
from backend.agents.skill_metadata import parse_skill_metadata
|
||||
from backend.agents.skills_manager import SkillsManager
|
||||
from backend.agents.team_pipeline_config import (
|
||||
ensure_team_pipeline_config,
|
||||
load_team_pipeline_config,
|
||||
update_active_analysts,
|
||||
)
|
||||
|
||||
|
||||
def test_parse_skill_metadata_extended_frontmatter(tmp_path):
|
||||
skill_dir = tmp_path / "demo_skill"
|
||||
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||
(skill_dir / "SKILL.md").write_text(
|
||||
"---\n"
|
||||
"name: demo_skill\n"
|
||||
"description: Demo description\n"
|
||||
"tools:\n"
|
||||
" - technical\n"
|
||||
"---\n\n"
|
||||
"# Demo Skill\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
parsed = parse_skill_metadata(skill_dir, source="builtin")
|
||||
|
||||
assert parsed.skill_name == "demo_skill"
|
||||
assert parsed.description == "Demo description"
|
||||
assert parsed.tools == ["technical"]
|
||||
|
||||
|
||||
def test_update_agent_skill_overrides(tmp_path):
|
||||
manager = SkillsManager(project_root=tmp_path)
|
||||
asset_dir = manager.get_agent_asset_dir("demo", "risk_manager")
|
||||
asset_dir.mkdir(parents=True, exist_ok=True)
|
||||
(asset_dir / "agent.yaml").write_text(
|
||||
"enabled_skills:\n"
|
||||
" - risk_review\n"
|
||||
"disabled_skills:\n"
|
||||
" - old_skill\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = manager.update_agent_skill_overrides(
|
||||
config_name="demo",
|
||||
agent_id="risk_manager",
|
||||
enable=["extra_guard"],
|
||||
disable=["risk_review"],
|
||||
)
|
||||
|
||||
assert result["enabled_skills"] == ["extra_guard"]
|
||||
assert result["disabled_skills"] == ["old_skill", "risk_review"]
|
||||
|
||||
|
||||
def test_skills_enable_disable_and_list(monkeypatch, tmp_path):
|
||||
builtin_root = tmp_path / "backend" / "skills" / "builtin"
|
||||
for name in ("risk_review", "extra_guard"):
|
||||
skill_dir = builtin_root / name
|
||||
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||
(skill_dir / "SKILL.md").write_text(
|
||||
f"---\nname: {name}\ndescription: {name} desc\n---\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
printed = []
|
||||
monkeypatch.setattr(cli, "get_project_root", lambda: tmp_path)
|
||||
monkeypatch.setattr(cli.console, "print", lambda value: printed.append(value))
|
||||
|
||||
cli.skills_enable(agent_id="risk_manager", skill="extra_guard", config_name="demo")
|
||||
cli.skills_disable(agent_id="risk_manager", skill="risk_review", config_name="demo")
|
||||
cli.skills_list(config_name="demo", agent_id="risk_manager")
|
||||
|
||||
text_dump = "\n".join(str(item) for item in printed)
|
||||
assert "Enabled" in text_dump
|
||||
assert "Disabled" in text_dump
|
||||
assert any(getattr(item, "title", None) == "Skill Catalog" for item in printed)
|
||||
|
||||
|
||||
def test_install_external_skill_for_agent(tmp_path):
|
||||
manager = SkillsManager(project_root=tmp_path)
|
||||
skill_dir = tmp_path / "downloaded" / "new_skill"
|
||||
skill_dir.mkdir(parents=True, exist_ok=True)
|
||||
(skill_dir / "SKILL.md").write_text(
|
||||
"---\n"
|
||||
"name: new_skill\n"
|
||||
"description: external skill\n"
|
||||
"---\n\n"
|
||||
"# New Skill\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = manager.install_external_skill_for_agent(
|
||||
config_name="demo",
|
||||
agent_id="risk_manager",
|
||||
source=str(skill_dir),
|
||||
activate=True,
|
||||
)
|
||||
|
||||
assert result["skill_name"] == "new_skill"
|
||||
target = manager.get_agent_local_root("demo", "risk_manager") / "new_skill"
|
||||
assert target.exists()
|
||||
|
||||
|
||||
def test_team_pipeline_active_analyst_updates(tmp_path):
|
||||
project_root = tmp_path
|
||||
ensure_team_pipeline_config(
|
||||
project_root=project_root,
|
||||
config_name="demo",
|
||||
default_analysts=["fundamentals_analyst", "technical_analyst"],
|
||||
)
|
||||
update_active_analysts(
|
||||
project_root=project_root,
|
||||
config_name="demo",
|
||||
available_analysts=["fundamentals_analyst", "technical_analyst"],
|
||||
remove=["technical_analyst"],
|
||||
)
|
||||
config = load_team_pipeline_config(project_root, "demo")
|
||||
assert config["discussion"]["active_analysts"] == ["fundamentals_analyst"]
|
||||
Reference in New Issue
Block a user