Align branding, prompts, and deployment tooling

This commit is contained in:
2026-03-28 22:16:56 +08:00
parent 4aa69650e8
commit 4295293a21
90 changed files with 1320 additions and 2044 deletions

View File

@@ -13,7 +13,7 @@ class _DummyToolkit:
return ""
def test_workspace_manager_creates_extended_agent_files(tmp_path):
def test_workspace_manager_creates_core_agent_files(tmp_path):
manager = WorkspaceManager(project_root=tmp_path)
manager.initialize_default_assets(
@@ -27,7 +27,7 @@ def test_workspace_manager_creates_extended_agent_files(tmp_path):
assert (asset_dir / "PROFILE.md").exists()
assert (asset_dir / "AGENTS.md").exists()
assert (asset_dir / "MEMORY.md").exists()
assert (asset_dir / "HEARTBEAT.md").exists()
assert (asset_dir / "POLICY.md").exists()
assert (asset_dir / "agent.yaml").exists()
assert (asset_dir / "skills" / "installed").is_dir()
assert (asset_dir / "skills" / "active").is_dir()
@@ -35,6 +35,22 @@ def test_workspace_manager_creates_extended_agent_files(tmp_path):
assert (asset_dir / "skills" / "local").is_dir()
def test_workspace_manager_seeds_risk_prompt_content(tmp_path):
manager = WorkspaceManager(project_root=tmp_path)
manager.initialize_default_assets(
config_name="demo",
agent_ids=["risk_manager"],
analyst_personas={},
)
asset_dir = tmp_path / "runs" / "demo" / "agents" / "risk_manager"
soul = (asset_dir / "SOUL.md").read_text(encoding="utf-8")
guide = (asset_dir / "AGENTS.md").read_text(encoding="utf-8")
assert "风险管理经理" in soul
assert "优先使用可用的风险工具量化集中度" in guide
def test_agent_workspace_config_controls_prompt_files(tmp_path, monkeypatch):
manager = WorkspaceManager(project_root=tmp_path)
manager.initialize_default_assets(
@@ -72,6 +88,32 @@ def test_agent_workspace_config_controls_prompt_files(tmp_path, monkeypatch):
assert "profile-line" not in prompt
def test_prompt_is_built_from_workspace_defaults_without_system_templates(tmp_path, monkeypatch):
manager = WorkspaceManager(project_root=tmp_path)
manager.initialize_default_assets(
config_name="demo",
agent_ids=["portfolio_manager"],
analyst_personas={},
)
from backend.agents import prompt_factory
monkeypatch.setattr(
prompt_factory,
"SkillsManager",
lambda: SkillsManager(project_root=tmp_path),
)
prompt = build_agent_system_prompt(
agent_id="portfolio_manager",
config_name="demo",
toolkit=_DummyToolkit(),
)
assert "投资组合经理" in prompt
assert "使用 `make_decision` 工具记录每个股票的最终决策" in prompt
def test_skills_manager_applies_agent_level_skill_toggles(tmp_path):
builtin_root = tmp_path / "backend" / "skills" / "builtin"
for skill_name in ("risk_review", "extra_guard"):

View File

@@ -8,24 +8,6 @@ import pytest
from backend.services import gateway_cycle_support, gateway_runtime_support
class _DummyDashboard:
def __init__(self):
self.updated = []
self.tickers = []
self.initial_cash = None
self.enable_memory = False
self.days_total = 0
def update(self, **kwargs):
self.updated.append(kwargs)
def stop(self):
return None
def print_final_summary(self):
return None
class _DummyScheduler:
def __init__(self):
self.calls = []
@@ -128,7 +110,6 @@ def make_gateway_stub():
},
storage=_DummyStorage(),
state_sync=_DummyStateSync(),
_dashboard=_DummyDashboard(),
_watchlist_ingest_task=None,
_market_status_task=None,
_backtest_task=None,

View File

@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
"""Tests for HeartbeatHook."""
import tempfile
from pathlib import Path
import pytest
from backend.agents.base.hooks import HeartbeatHook
class TestHeartbeatHook:
"""Tests for HeartbeatHook._read_heartbeat_content."""
def test_read_heartbeat_content_with_content(self, tmp_path):
"""Test reading HEARTBEAT.md when it exists and has content."""
ws_dir = tmp_path / "analyst_workspace"
ws_dir.mkdir()
hb_file = ws_dir / "HEARTBEAT.md"
hb_file.write_text("# 定期主动检查\n\n- [ ] 持仓是否健康\n", encoding="utf-8")
hook = HeartbeatHook(workspace_dir=ws_dir)
content = hook._read_heartbeat_content()
assert content is not None
assert "# 定期主动检查" in content
assert "持仓是否健康" in content
def test_read_heartbeat_content_absent(self, tmp_path):
"""Test reading when HEARTBEAT.md does not exist."""
ws_dir = tmp_path / "analyst_workspace"
ws_dir.mkdir()
hook = HeartbeatHook(workspace_dir=ws_dir)
content = hook._read_heartbeat_content()
assert content is None
def test_read_heartbeat_content_empty(self, tmp_path):
"""Test reading when HEARTBEAT.md is empty."""
ws_dir = tmp_path / "analyst_workspace"
ws_dir.mkdir()
hb_file = ws_dir / "HEARTBEAT.md"
hb_file.write_text("", encoding="utf-8")
hook = HeartbeatHook(workspace_dir=ws_dir)
content = hook._read_heartbeat_content()
assert content is None
def test_read_heartbeat_content_whitespace_only(self, tmp_path):
"""Test reading when HEARTBEAT.md contains only whitespace."""
ws_dir = tmp_path / "analyst_workspace"
ws_dir.mkdir()
hb_file = ws_dir / "HEARTBEAT.md"
hb_file.write_text(" \n\n ", encoding="utf-8")
hook = HeartbeatHook(workspace_dir=ws_dir)
content = hook._read_heartbeat_content()
assert content is None
def test_completed_flag_path(self, tmp_path):
"""Test that completion flag is placed in workspace directory."""
ws_dir = tmp_path / "analyst_workspace"
ws_dir.mkdir()
hook = HeartbeatHook(workspace_dir=ws_dir)
assert hook._completed_flag == ws_dir / ".heartbeat_completed"

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""Tests for market ingest watermark handling."""
from backend.data import market_ingest
class _FakeStore:
def __init__(self, *, last_news_fetch=None, latest_news_date=None):
self._watermarks = {
"symbol": "AAPL",
"last_price_fetch": None,
"last_news_fetch": last_news_fetch,
}
self._latest_news_date = latest_news_date
self.updated = []
def get_ticker_watermarks(self, symbol):
return dict(self._watermarks)
def get_latest_news_date(self, symbol):
return self._latest_news_date
def upsert_ticker(self, **kwargs):
return None
def upsert_ohlc(self, symbol, rows, source="polygon"):
return len(rows)
def upsert_news(self, symbol, rows, source="polygon"):
return len(rows)
def update_fetch_watermark(self, **kwargs):
self.updated.append(kwargs)
def test_refresh_news_incremental_does_not_advance_watermark_without_news(monkeypatch):
store = _FakeStore(last_news_fetch="2026-03-28", latest_news_date="2026-03-28")
monkeypatch.setattr(market_ingest, "fetch_ticker_details", lambda ticker: {"name": ticker, "sic_description": None, "active": True})
class _Router:
def get_company_news(self, **kwargs):
return [], "polygon"
monkeypatch.setattr(market_ingest, "DataProviderRouter", lambda: _Router())
monkeypatch.setattr(market_ingest, "align_news_for_symbol", lambda store, ticker: 0)
result = market_ingest.refresh_news_incremental(
"AAPL",
end_date="2026-03-29",
store=store,
)
assert result["start_news_date"] == "2026-03-29"
assert result["news"] == 0
assert store.updated[-1]["news_date"] is None
def test_refresh_news_incremental_clamps_future_watermark_to_latest_stored_date(monkeypatch):
store = _FakeStore(last_news_fetch="2026-03-30", latest_news_date="2026-03-28")
captured = {}
monkeypatch.setattr(market_ingest, "fetch_ticker_details", lambda ticker: {"name": ticker, "sic_description": None, "active": True})
class _Router:
def get_company_news(self, **kwargs):
captured.update(kwargs)
return [], "polygon"
monkeypatch.setattr(market_ingest, "DataProviderRouter", lambda: _Router())
monkeypatch.setattr(market_ingest, "align_news_for_symbol", lambda store, ticker: 0)
result = market_ingest.refresh_news_incremental(
"AAPL",
end_date="2026-03-29",
store=store,
)
assert result["start_news_date"] == "2026-03-29"
assert captured["start_date"] == "2026-03-29"
assert captured["end_date"] == "2026-03-29"