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:
14
CLAUDE.md
14
CLAUDE.md
@@ -16,12 +16,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
# 安装依赖
|
# 安装依赖
|
||||||
uv pip install -e .
|
uv pip install -e .
|
||||||
|
|
||||||
# 运行命令
|
# 运行回测 / 实盘
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01 # 回测模式
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory # 带记忆回测
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01 --enable-memory
|
||||||
evotraders live # 实盘交易
|
python backend/main.py --mode live --config-name live
|
||||||
evotraders live -t 22:30 # 定时每日交易
|
python backend/main.py --mode live --config-name live --trigger-time 22:30
|
||||||
evotraders frontend # 启动可视化界面
|
|
||||||
|
# 启动前端
|
||||||
|
cd frontend && npm run dev
|
||||||
|
|
||||||
# 开发服务器
|
# 开发服务器
|
||||||
./start-dev.sh # 启动全部 4 个微服务 (agent, runtime, trading, news)
|
./start-dev.sh # 启动全部 4 个微服务 (agent, runtime, trading, news)
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
大时代 is an open-source financial trading agent framework that combines multi-agent collaboration, run-scoped workspaces, and memory to support both backtests and live trading workflows.
|
大时代 is an open-source financial trading agent framework that combines multi-agent collaboration, run-scoped workspaces, and memory to support both backtests and live trading workflows.
|
||||||
|
|
||||||
The repository name and CLI entrypoints still use `evotraders` for compatibility, but the product-facing branding now follows the 大时代 naming used by the reference branch.
|
The repository name still uses `evotraders`, but the product-facing branding now follows the 大时代 naming used by the reference branch.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ This starts:
|
|||||||
Then start the frontend in another terminal:
|
Then start the frontend in another terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders frontend
|
cd frontend && npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://localhost:5173`.
|
Open `http://localhost:5173`.
|
||||||
@@ -233,35 +233,29 @@ python -m uvicorn backend.apps.runtime_service:app --host 0.0.0.0 --port 8003 --
|
|||||||
# compatibility gateway path, not the recommended primary dev entrypoint
|
# compatibility gateway path, not the recommended primary dev entrypoint
|
||||||
python -m backend.main --mode live --host 0.0.0.0 --port 8765
|
python -m backend.main --mode live --host 0.0.0.0 --port 8765
|
||||||
```
|
```
|
||||||
|
### 4. Run backtest or live mode
|
||||||
### 4. Run backtest or live mode from CLI
|
|
||||||
|
|
||||||
Backtest:
|
Backtest:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01 --enable-memory
|
||||||
evotraders backtest --config-name smoke_fullstack --start 2025-11-01 --end 2025-12-01
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Live:
|
Live:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders live
|
python backend/main.py --mode live --config-name live
|
||||||
evotraders live --enable-memory
|
python backend/main.py --mode live --config-name live --enable-memory
|
||||||
evotraders live --schedule-mode intraday --interval-minutes 60
|
python backend/main.py --mode live --config-name live --interval-minutes 60
|
||||||
evotraders live --trigger-time 22:30
|
python backend/main.py --mode live --config-name live --trigger-time 22:30
|
||||||
```
|
```
|
||||||
|
|
||||||
Help:
|
Help:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders --help
|
python backend/main.py --help
|
||||||
evotraders backtest --help
|
|
||||||
evotraders live --help
|
|
||||||
evotraders frontend --help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Offline backtest data
|
### Offline backtest data
|
||||||
|
|
||||||
If you want a quick backtest demo without external market APIs, download the offline bundle and unzip it into `backend/data`:
|
If you want a quick backtest demo without external market APIs, download the offline bundle and unzip it into `backend/data`:
|
||||||
@@ -381,11 +375,7 @@ trigger_time: "09:30"
|
|||||||
enable_memory: false
|
enable_memory: false
|
||||||
```
|
```
|
||||||
|
|
||||||
Initialize run-scoped assets with:
|
Run-scoped workspaces are created automatically at runtime. No manual initialization is required.
|
||||||
|
|
||||||
```bash
|
|
||||||
evotraders init-workspace --config-name my_run
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -399,8 +389,7 @@ evotraders/
|
|||||||
│ ├── apps/ # split service surfaces
|
│ ├── apps/ # split service surfaces
|
||||||
│ ├── core/ # pipeline, scheduler, state sync
|
│ ├── core/ # pipeline, scheduler, state sync
|
||||||
│ ├── runtime/ # runtime manager and agent runtime state
|
│ ├── runtime/ # runtime manager and agent runtime state
|
||||||
│ ├── services/ # gateway, market/storage/db services
|
│ └── services/ # gateway, market/storage/db services
|
||||||
│ └── cli.py # Typer CLI entrypoint
|
|
||||||
├── frontend/ # React + Vite UI
|
├── frontend/ # React + Vite UI
|
||||||
├── shared/ # shared clients and schemas for split services
|
├── shared/ # shared clients and schemas for split services
|
||||||
├── runs/ # run-scoped state and dashboards
|
├── runs/ # run-scoped state and dashboards
|
||||||
|
|||||||
30
README_zh.md
30
README_zh.md
@@ -184,7 +184,7 @@ python3 scripts/smoke_evo_runtime.py --agent-id fundamentals_analyst
|
|||||||
然后在另一个终端启动前端:
|
然后在另一个终端启动前端:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders frontend
|
cd frontend && npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
访问 `http://localhost:5173`。
|
访问 `http://localhost:5173`。
|
||||||
@@ -203,34 +203,29 @@ python -m backend.main --mode live --host 0.0.0.0 --port 8765
|
|||||||
仓库里部署脚本使用的 `production` 只是一个示例 run label,不应再把它理解成
|
仓库里部署脚本使用的 `production` 只是一个示例 run label,不应再把它理解成
|
||||||
系统规定的根目录运行目录名。
|
系统规定的根目录运行目录名。
|
||||||
|
|
||||||
### 4. 使用 CLI 运行回测或实盘
|
### 4. 运行回测或实盘
|
||||||
|
|
||||||
回测:
|
回测:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01
|
||||||
evotraders backtest --start 2025-11-01 --end 2025-12-01 --enable-memory
|
python backend/main.py --mode backtest --config-name smoke_fullstack --start-date 2025-11-01 --end-date 2025-12-01 --enable-memory
|
||||||
evotraders backtest --config-name smoke_fullstack --start 2025-11-01 --end 2025-12-01
|
|
||||||
```
|
```
|
||||||
|
|
||||||
实盘:
|
实盘:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders live
|
python backend/main.py --mode live --config-name live
|
||||||
evotraders live --enable-memory
|
python backend/main.py --mode live --config-name live --enable-memory
|
||||||
evotraders live --schedule-mode intraday --interval-minutes 60
|
python backend/main.py --mode live --config-name live --interval-minutes 60
|
||||||
evotraders live --trigger-time 22:30
|
python backend/main.py --mode live --config-name live --trigger-time 22:30
|
||||||
```
|
```
|
||||||
|
|
||||||
帮助:
|
帮助:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
evotraders --help
|
python backend/main.py --help
|
||||||
evotraders backtest --help
|
|
||||||
evotraders live --help
|
|
||||||
evotraders frontend --help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 离线回测数据
|
### 离线回测数据
|
||||||
|
|
||||||
如果只是想快速体验回测,不依赖外部行情 API,可以下载离线数据包并解压到 `backend/data`:
|
如果只是想快速体验回测,不依赖外部行情 API,可以下载离线数据包并解压到 `backend/data`:
|
||||||
@@ -348,11 +343,7 @@ trigger_time: "09:30"
|
|||||||
enable_memory: false
|
enable_memory: false
|
||||||
```
|
```
|
||||||
|
|
||||||
初始化一个 run 运行资产目录:
|
运行时作用域工作区会在首次运行 pipeline 或服务时自动创建,无需手动初始化。
|
||||||
|
|
||||||
```bash
|
|
||||||
evotraders init-workspace --config-name my_run
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -367,7 +358,6 @@ evotraders/
|
|||||||
│ ├── core/ # pipeline、scheduler、state sync
|
│ ├── core/ # pipeline、scheduler、state sync
|
||||||
│ ├── runtime/ # runtime manager 和 agent runtime state
|
│ ├── runtime/ # runtime manager 和 agent runtime state
|
||||||
│ ├── services/ # gateway、market/storage/db 服务
|
│ ├── services/ # gateway、market/storage/db 服务
|
||||||
│ └── cli.py # Typer CLI 入口
|
|
||||||
├── frontend/ # React + Vite 前端
|
├── frontend/ # React + Vite 前端
|
||||||
├── shared/ # 拆分服务共用 client 和 schema
|
├── shared/ # 拆分服务共用 client 和 schema
|
||||||
├── runs/ # run-scoped 状态和 dashboards
|
├── runs/ # run-scoped 状态和 dashboards
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
|||||||
self.agent_id = agent_id
|
self.agent_id = agent_id
|
||||||
self.config_name = config_name
|
self.config_name = config_name
|
||||||
self.workspace_dir = Path(workspace_dir)
|
self.workspace_dir = Path(workspace_dir)
|
||||||
|
self.workspace_id = config_name
|
||||||
|
self.config = {"config_name": config_name}
|
||||||
self._skills_manager = skills_manager or SkillsManager()
|
self._skills_manager = skills_manager or SkillsManager()
|
||||||
self._env_context = env_context
|
self._env_context = env_context
|
||||||
self._prompt_files = prompt_files
|
self._prompt_files = prompt_files
|
||||||
@@ -328,6 +330,17 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
|||||||
# Call parent (which may be ToolGuardMixin's _reasoning)
|
# Call parent (which may be ToolGuardMixin's _reasoning)
|
||||||
return await super()._reasoning(**kwargs)
|
return await super()._reasoning(**kwargs)
|
||||||
|
|
||||||
|
def reload_runtime_assets(self, active_skill_dirs: Optional[List[Path]] = None) -> None:
|
||||||
|
"""Reload toolkit and system prompt from current run assets.
|
||||||
|
|
||||||
|
Refreshes prompt files from workspace config and rebuilds the toolkit.
|
||||||
|
"""
|
||||||
|
# Rebuild system prompt (also refreshes _agent_config and _prompt_files)
|
||||||
|
self.rebuild_sys_prompt()
|
||||||
|
|
||||||
|
# Reload skills/toolkit
|
||||||
|
self.reload_skills(active_skill_dirs=active_skill_dirs)
|
||||||
|
|
||||||
def reload_skills(self, active_skill_dirs: Optional[List[Path]] = None) -> None:
|
def reload_skills(self, active_skill_dirs: Optional[List[Path]] = None) -> None:
|
||||||
"""Reload skills at runtime.
|
"""Reload skills at runtime.
|
||||||
|
|
||||||
@@ -497,6 +510,10 @@ class EvoAgent(ToolGuardMixin, ReActAgent):
|
|||||||
# Reload agent config in case it changed
|
# Reload agent config in case it changed
|
||||||
self._agent_config = self._load_agent_config()
|
self._agent_config = self._load_agent_config()
|
||||||
|
|
||||||
|
# Refresh prompt_files from updated config
|
||||||
|
if "prompt_files" in self._agent_config:
|
||||||
|
self._prompt_files = list(self._agent_config["prompt_files"])
|
||||||
|
|
||||||
# Rebuild prompt
|
# Rebuild prompt
|
||||||
self._sys_prompt = self._build_system_prompt()
|
self._sys_prompt = self._build_system_prompt()
|
||||||
|
|
||||||
|
|||||||
1594
backend/cli.py
1594
backend/cli.py
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@ def test_agent_service_status_includes_scope_metadata(tmp_path):
|
|||||||
payload = response.json()
|
payload = response.json()
|
||||||
assert payload["scope"]["design_time_registry"]["root"] == str(tmp_path / "workspaces")
|
assert payload["scope"]["design_time_registry"]["root"] == str(tmp_path / "workspaces")
|
||||||
assert payload["scope"]["runtime_assets"]["root"] == str(tmp_path / "runs")
|
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):
|
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,
|
workspace_dir=workspace_dir,
|
||||||
model=DummyModel(),
|
model=DummyModel(),
|
||||||
formatter=DummyFormatter(),
|
formatter=DummyFormatter(),
|
||||||
|
prompt_files=["SOUL.md"],
|
||||||
skills_manager=type(
|
skills_manager=type(
|
||||||
"SkillsManagerStub",
|
"SkillsManagerStub",
|
||||||
(),
|
(),
|
||||||
@@ -248,11 +249,15 @@ def test_pipeline_create_runtime_analyst_uses_evo_agent_when_enabled(monkeypatch
|
|||||||
created = {}
|
created = {}
|
||||||
|
|
||||||
class DummyEvoAgent:
|
class DummyEvoAgent:
|
||||||
|
name = "test_analyst"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
created.update(kwargs)
|
created.update(kwargs)
|
||||||
self.toolkit = None
|
self.toolkit = None
|
||||||
|
|
||||||
class DummyAnalystAgent:
|
class DummyAnalystAgent:
|
||||||
|
name = "test_analyst"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
created.update(kwargs)
|
created.update(kwargs)
|
||||||
self.toolkit = None
|
self.toolkit = None
|
||||||
@@ -308,11 +313,15 @@ def test_pipeline_create_runtime_analyst_uses_legacy_when_not_in_evo_ids(monkeyp
|
|||||||
created = {}
|
created = {}
|
||||||
|
|
||||||
class DummyEvoAgent:
|
class DummyEvoAgent:
|
||||||
|
name = "test_analyst"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
created.update(kwargs)
|
created.update(kwargs)
|
||||||
self.toolkit = None
|
self.toolkit = None
|
||||||
|
|
||||||
class DummyAnalystAgent:
|
class DummyAnalystAgent:
|
||||||
|
name = "test_analyst"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
created.update(kwargs)
|
created.update(kwargs)
|
||||||
self.toolkit = None
|
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},
|
{"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()
|
||||||
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"}],
|
"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):
|
def list_cron_jobs(self):
|
||||||
return {"jobs": [{"id": "job-1", "name": "Daily sync"}]}
|
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):
|
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():
|
def test_openclaw_service_routes_are_exposed():
|
||||||
@@ -85,17 +123,17 @@ def test_openclaw_service_read_routes():
|
|||||||
assert status.status_code == 200
|
assert status.status_code == 200
|
||||||
assert status.json()["status"] == "operational"
|
assert status.json()["status"] == "operational"
|
||||||
assert openclaw_status.status_code == 200
|
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 sessions.status_code == 200
|
||||||
assert len(sessions.json()["sessions"]) == 2
|
assert len(sessions.json()["sessions"]) == 2
|
||||||
assert session.status_code == 200
|
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.status_code == 200
|
||||||
assert history.json()["limit"] == 5
|
assert len(history.json()["events"]) == 1
|
||||||
assert cron.status_code == 200
|
assert cron.status_code == 200
|
||||||
assert cron.json()["jobs"][0]["id"] == "job-1"
|
assert cron.json()["jobs"][0]["id"] == "job-1"
|
||||||
assert approvals.status_code == 200
|
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():
|
def test_openclaw_service_session_404():
|
||||||
|
|||||||
@@ -38,8 +38,13 @@ def test_runtime_service_health_and_status(monkeypatch):
|
|||||||
assert health_response.json() == {
|
assert health_response.json() == {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"service": "runtime-service",
|
"service": "runtime-service",
|
||||||
"gateway_running": False,
|
"gateway": {
|
||||||
"gateway_port": 9876,
|
"running": False,
|
||||||
|
"port": 9876,
|
||||||
|
"pid": None,
|
||||||
|
"process_status": "not_running",
|
||||||
|
"returncode": None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert status_response.status_code == 200
|
assert status_response.status_code == 200
|
||||||
assert status_response.json() == {
|
assert status_response.json() == {
|
||||||
@@ -48,6 +53,8 @@ def test_runtime_service_health_and_status(monkeypatch):
|
|||||||
"runtime": {
|
"runtime": {
|
||||||
"gateway_running": False,
|
"gateway_running": False,
|
||||||
"gateway_port": 9876,
|
"gateway_port": 9876,
|
||||||
|
"gateway_pid": None,
|
||||||
|
"gateway_process_status": "not_running",
|
||||||
"has_runtime_manager": True,
|
"has_runtime_manager": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -365,6 +372,8 @@ def test_runtime_service_start_stop_lifecycle_contract(monkeypatch, tmp_path):
|
|||||||
return self.context
|
return self.context
|
||||||
|
|
||||||
class _DummyProcess:
|
class _DummyProcess:
|
||||||
|
pid = 12345
|
||||||
|
|
||||||
def poll(self):
|
def poll(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -574,6 +583,8 @@ def test_start_runtime_restore_reuses_historical_run_id(monkeypatch, tmp_path):
|
|||||||
return self.context
|
return self.context
|
||||||
|
|
||||||
class _DummyProcess:
|
class _DummyProcess:
|
||||||
|
pid = 12345
|
||||||
|
|
||||||
def poll(self):
|
def poll(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ class _DummyAsyncClient:
|
|||||||
|
|
||||||
async def get(self, path, params=None):
|
async def get(self, path, params=None):
|
||||||
self.calls.append(("get", path, params))
|
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})
|
return _DummyResponse({"path": path, "params": params})
|
||||||
|
|
||||||
async def post(self, path, json=None):
|
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"]
|
|
||||||
@@ -60,6 +60,7 @@ docker-sandbox = [
|
|||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest>=8.3.3",
|
"pytest>=8.3.3",
|
||||||
|
"pytest-asyncio>=0.24.0",
|
||||||
"ruff>=0.6.9",
|
"ruff>=0.6.9",
|
||||||
"black>=25.0.0"
|
"black>=25.0.0"
|
||||||
]
|
]
|
||||||
@@ -70,9 +71,6 @@ Repository = "https://github.com/agentscope-ai/agentscope-samples/evotraders"
|
|||||||
Documentation = "https://github.com/agentscope-ai/agentscope-samples/evotraders/README.md"
|
Documentation = "https://github.com/agentscope-ai/agentscope-samples/evotraders/README.md"
|
||||||
"Bug Tracker" = "https://github.com/agentscope-ai/agentscope-samples/issues"
|
"Bug Tracker" = "https://github.com/agentscope-ai/agentscope-samples/issues"
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
evotraders = "backend.cli:app"
|
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["backend*", "shared*"]
|
include = ["backend*", "shared*"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user