ReMe/tests/unit/test_base_agent_wrapper.py
jinliyl 46adb5ae1e
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Tests ReMe / Unit Tests - py3.11 (push) Has been cancelled
Tests ReMe / Unit Tests - py3.12 (push) Has been cancelled
Tests ReMe / Unit Tests - py3.13 (push) Has been cancelled
Windows Smoke / CLI smoke - py3.11 (push) Has been cancelled
feat: add daily paper cookbook and DingTalk agent integration (#385)
* feat(daily-paper): add daily paper cookbook workflow with schema and tests

- Introduce daily paper schema types (DailyBriefOutput, PaperInfo, PaperNoteOutput, etc.)
- Create daily paper cookbook module with analyze, collect, digest, rank, and select steps
- Add cookbook entry point and integrate into main steps module
- Replace job config export with daily brief output in schema exports
- Add comprehensive unit tests covering pipeline, filtering, and output generation
- Update dependencies including openai-codex and pypdf packages
- Configure standalone daily paper cron job with proper scheduling and routing

* test(daily_paper): update tests to use Claude Code wrapper exclusively

- Add test to verify web search is disallowed by default in Claude Code
- Update imports to include DailyBriefOutput, PaperNoteOutput, and PaperSelection schemas
- Change test name from standalone_config_has_backend_split to reflect Claude Code only usage
- Remove default agent wrapper and configure all steps to use Claude Code wrapper
- Rename select_wrapper to cc_wrapper for clarity and consistency
- Remove duplicate Claude Code wrapper initialization
- Update test assertions to verify output schema usage matches expected sequence
- Remove unused as_llm component from standalone configuration test

* refactor(agent-wrapper): simplify skill resolution logic across all wrappers

- Replace duplicate skill resolution code with centralized _resolve_project_skills method
- Add project_path property with configurable relative path resolution
- Introduce proper validation for skill names and directory existence
- Change Codex wrapper to use project_path instead of workspace_path for skills
- Add SKILL.md requirement validation for project skills
- Remove redundant skill processing logic from individual wrappers

* feat(daily_paper): add daily paper workflow with PDF analysis and brief generation

- Implement shared state management and file helpers for daily-paper steps
- Add PDF download and text extraction capabilities with arXiv integration
- Create paper collection step with Hugging Face weekly/monthly rankings
- Build ranking system using reciprocal-rank fusion with memory keyword scoring
- Add Claude Code integration for paper analysis and detailed note generation
- Implement digest step to create final five-minute brief from detailed notes
- Add configuration for standalone daily cookbook application with cron scheduling
- Create typed schema for paper information, selection, and output formats
- Add atomic file writing with temporary file safety mechanisms
- Implement exclusion logic for previously recommended papers and daily filters

* feat(daily_paper): add DingTalk notification integration and enhance logging

- Integrate DingTalk markdown send step to notify groups about daily paper briefs
- Add comprehensive logging throughout daily paper workflow including start/finish events
- Update daily paper analysis prompt to include code repository context requirement
- Configure DingTalk notification in daily_cookbook.yaml with app credentials
- Add dingtalk-stream dependency for proactive message API integration
- Enhance daily paper README with DingTalk notification section and updated flow chart
- Implement detailed logging for each step including paper processing and agent calls
- Add test coverage for DingTalk markdown sending functionality and configuration
- Update pre-commit config to exclude skills directory from checks
- Add .claude/skills to gitignore for local development environment

* refactor(dingtalk): move dingtalk_stream import to local scope and improve code safety

- Moved global dingtalk_stream import to local scope in send.py to avoid eager loading
- Added dynamic import with error handling for optional dependency cases
- Updated test suite to verify lazy loading behavior works correctly
- Fixed markdown title generation by using safe variable naming in wait.py
- Enhanced test coverage for arxiv PDF download caching functionality
- Updated application context initialization with proper resource directory configuration
- Modified paper metadata to include source PDF path reference in output files

* refactor(daily_paper): remove manifest system and store selection metadata in digest files

- Remove JSON manifest creation and storage functionality
- Store selection data directly in digest file frontmatter instead of separate manifest files
- Add load_saved_selection method to rebuild selection from digest and paper-note metadata
- Update README documentation to reflect new cookbook workflow architecture
- Modify test cases to verify selection metadata in digest files instead of manifest JSON
- Remove unused json import from multiple daily paper modules
- Integrate PaperSelection schema for proper data validation in stored metadata

* docs(daily_paper): add bilingual cookbook guides
2026-07-22 19:17:01 +08:00

107 lines
4 KiB
Python

"""Tests for shared agent wrapper behavior."""
import sys
from unittest.mock import MagicMock
import pytest
from reme.components.agent_wrapper import AsAgentWrapper, BaseAgentWrapper, CcAgentWrapper, CodexAgentWrapper
from reme.components.agent_wrapper.as_agent_wrapper import WorkspaceBackend
from reme.components.agent_wrapper import base_agent_wrapper
from reme.components.application_context import ApplicationContext
from reme.components import base_component
class _VersionedAgentWrapper(BaseAgentWrapper):
SDK_PACKAGE = "example-agent-sdk"
async def reply(self, inputs, **kwargs) -> dict:
return {"inputs": inputs, "kwargs": kwargs}
def test_init_logs_sdk_version(monkeypatch):
"""An SDK-backed wrapper logs its installed distribution version."""
logger = MagicMock()
logger.bind.return_value = logger
monkeypatch.setattr(base_component, "get_logger", lambda: logger)
monkeypatch.setattr(base_agent_wrapper.metadata, "version", lambda package: "1.2.3")
_VersionedAgentWrapper(name="versioned")
logger.info.assert_called_once_with("Agent SDK package=example-agent-sdk version=1.2.3")
def test_init_logs_unknown_when_sdk_distribution_metadata_is_missing(monkeypatch):
"""Missing distribution metadata does not prevent wrapper initialization."""
logger = MagicMock()
logger.bind.return_value = logger
monkeypatch.setattr(base_component, "get_logger", lambda: logger)
def missing_version(package):
raise base_agent_wrapper.metadata.PackageNotFoundError(package)
monkeypatch.setattr(base_agent_wrapper.metadata, "version", missing_version)
_VersionedAgentWrapper()
logger.info.assert_called_once_with("Agent SDK package=example-agent-sdk version=unknown")
@pytest.mark.parametrize(
("wrapper_class", "sdk_package"),
[
(AsAgentWrapper, "agentscope"),
(CcAgentWrapper, "claude-agent-sdk"),
(CodexAgentWrapper, "openai-codex"),
],
)
def test_agent_wrappers_declare_sdk_package(wrapper_class, sdk_package):
"""Each concrete backend identifies the distribution that provides its SDK."""
assert wrapper_class.SDK_PACKAGE == sdk_package
def test_project_path_is_independent_from_runtime_workspace(tmp_path):
"""Project assets can live outside the runtime workspace."""
workspace = tmp_path / "project" / ".reme"
wrapper = _VersionedAgentWrapper(
app_context=ApplicationContext(workspace_dir=str(workspace)),
project_path="..",
)
assert wrapper.workspace_path == workspace
assert wrapper.project_path == tmp_path / "project"
assert wrapper.cwd == tmp_path / "project"
assert wrapper.project_skills_root == tmp_path / "project" / "skills"
@pytest.mark.parametrize("wrapper_class", [AsAgentWrapper, CcAgentWrapper, CodexAgentWrapper])
def test_agent_wrappers_share_project_skill_resolution(tmp_path, wrapper_class):
"""Every backend resolves selected skills through the base project root."""
workspace = tmp_path / "project" / ".reme"
skill = tmp_path / "project" / "skills" / "one"
skill.mkdir(parents=True)
(skill / "SKILL.md").write_text("# one", encoding="utf-8")
kwargs = {
"app_context": ApplicationContext(workspace_dir=str(workspace)),
"project_path": "..",
}
if wrapper_class is AsAgentWrapper:
kwargs["as_llm"] = ""
wrapper = wrapper_class(**kwargs)
assert wrapper._resolve_project_skills(["one", "one"]) == {"one": skill} # pylint: disable=protected-access
@pytest.mark.asyncio
async def test_agentscope_backend_passes_configured_environment_to_bash(tmp_path, monkeypatch):
"""AgentScope subprocesses receive config environment values explicitly."""
monkeypatch.setenv("REME_AGENT_ENV_TEST", "parent")
backend = WorkspaceBackend(str(tmp_path), {"REME_AGENT_ENV_TEST": "configured"})
result = await backend.exec_shell(
[sys.executable, "-c", "import os; print(os.environ['REME_AGENT_ENV_TEST'])"],
cwd=str(tmp_path),
)
assert result.exit_code == 0
assert result.stdout == b"configured\n"