mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-07 13:52:42 +08:00
- Add structured runtime config files (AGENT_PROFILE.md, AGENT_WORKFLOW.md, AGENT_HOOKS.md, USER_PREFERENCES.md, SYSTEM_TASKS.md, CURRENT_PERSONA.md) for persona, workflow, hooks, and system tasks - Implement agent_runtime_manager to load, validate, and render runtime config and system task prompts - Refactor agent initialization to use runtime-managed directories for skills, jobs, memory, and activity logs - Add AgentHooksMiddleware for structured pre/in/post hooks injection - Replace hardcoded system task prompts with template-driven rendering from SYSTEM_TASKS.md - Update tests to cover runtime config loading, migration, and system task prompt rendering - Update .gitignore to exclude config/agent/
193 lines
7.0 KiB
Python
193 lines
7.0 KiB
Python
import shutil
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from app.agent.runtime import AgentRuntimeConfigError, AgentRuntimeManager
|
|
|
|
|
|
class TestAgentRuntimeConfig(unittest.TestCase):
|
|
def setUp(self):
|
|
self._tempdir = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self._tempdir.cleanup)
|
|
self.temp_root = Path(self._tempdir.name)
|
|
self.agent_root = self.temp_root / "agent"
|
|
self.bundled_root = (
|
|
Path(__file__).resolve().parents[1] / "app" / "agent" / "runtime_defaults"
|
|
)
|
|
|
|
def _manager(self) -> AgentRuntimeManager:
|
|
return AgentRuntimeManager(
|
|
agent_root_dir=self.agent_root,
|
|
bundled_runtime_dir=self.bundled_root,
|
|
)
|
|
|
|
def test_load_runtime_config_syncs_defaults_and_parses_sections(self):
|
|
manager = self._manager()
|
|
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
self.assertEqual(runtime_config.active_persona, "default")
|
|
self.assertIn("professional, concise, restrained", runtime_config.profile_text)
|
|
self.assertIn(
|
|
"omitting `season` means subscribe to season 1 only",
|
|
runtime_config.workflow_text,
|
|
)
|
|
self.assertTrue((self.agent_root / "runtime" / "CURRENT_PERSONA.md").exists())
|
|
|
|
def test_legacy_root_markdown_is_migrated_to_memory_directory(self):
|
|
self.agent_root.mkdir(parents=True, exist_ok=True)
|
|
legacy_memory = self.agent_root / "MEMORY.md"
|
|
legacy_memory.write_text("# Legacy Memory\n", encoding="utf-8")
|
|
legacy_persona = self.agent_root / "CURRENT_PERSONA.md"
|
|
legacy_persona.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
---
|
|
active_persona: default
|
|
profile: personas/default/AGENT_PROFILE.md
|
|
workflow: personas/default/AGENT_WORKFLOW.md
|
|
hooks: personas/default/AGENT_HOOKS.md
|
|
system_tasks: system_tasks/SYSTEM_TASKS.md
|
|
user_preferences: USER_PREFERENCES.md
|
|
---
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
manager = self._manager()
|
|
manager.ensure_layout()
|
|
|
|
self.assertFalse(legacy_memory.exists())
|
|
self.assertTrue((self.agent_root / "memory" / "MEMORY.md").exists())
|
|
self.assertFalse(legacy_persona.exists())
|
|
self.assertTrue((self.agent_root / "runtime" / "CURRENT_PERSONA.md").exists())
|
|
|
|
def test_render_system_task_message_uses_unified_system_tasks_definition(self):
|
|
manager = self._manager()
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
message = runtime_config.render_system_task_message("heartbeat")
|
|
|
|
self.assertIn("[System Heartbeat]", message)
|
|
self.assertIn("List all jobs with status 'pending' or 'in_progress'.", message)
|
|
self.assertIn("Do NOT include greetings, explanations, or conversational text.", message)
|
|
self.assertIn("If no jobs were executed, output nothing.", message)
|
|
|
|
def test_render_system_task_message_renders_template_context(self):
|
|
manager = self._manager()
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
message = runtime_config.render_system_task_message(
|
|
"transfer_failed_retry",
|
|
template_context={
|
|
"history_ids_csv": "7",
|
|
"history_count": 1,
|
|
"history_id": 7,
|
|
},
|
|
)
|
|
|
|
self.assertIn("Failed transfer history record IDs: 7", message)
|
|
self.assertIn("Total failed records: 1", message)
|
|
self.assertIn("history_id=7", message)
|
|
|
|
def test_missing_template_context_raises_clear_error(self):
|
|
manager = self._manager()
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
with self.assertRaises(AgentRuntimeConfigError):
|
|
runtime_config.render_system_task_message("transfer_failed_retry")
|
|
|
|
def test_invalid_user_runtime_config_falls_back_to_bundled_defaults(self):
|
|
manager = self._manager()
|
|
manager.ensure_layout()
|
|
invalid_current = self.agent_root / "runtime" / "CURRENT_PERSONA.md"
|
|
invalid_current.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
---
|
|
active_persona: broken
|
|
profile: personas/default/AGENT_PROFILE.md
|
|
hooks: personas/default/AGENT_HOOKS.md
|
|
system_tasks: system_tasks/SYSTEM_TASKS.md
|
|
---
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
manager.invalidate_cache()
|
|
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
self.assertTrue(runtime_config.used_fallback)
|
|
self.assertEqual(runtime_config.active_persona, "default")
|
|
self.assertIn("已回退到内置默认配置", runtime_config.warnings[0])
|
|
|
|
def test_deprecated_phrase_warning_is_reported(self):
|
|
self.agent_root.mkdir(parents=True, exist_ok=True)
|
|
runtime_root = self.agent_root / "runtime"
|
|
shutil.copytree(self.bundled_root, runtime_root)
|
|
current_persona = runtime_root / "CURRENT_PERSONA.md"
|
|
current_persona.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
---
|
|
version: 1
|
|
active_persona: default
|
|
profile: personas/default/AGENT_PROFILE.md
|
|
workflow: personas/default/AGENT_WORKFLOW.md
|
|
hooks: personas/default/AGENT_HOOKS.md
|
|
user_preferences: USER_PREFERENCES.md
|
|
system_tasks: system_tasks/SYSTEM_TASKS.md
|
|
extra_context_files: []
|
|
deprecated_phrases:
|
|
- professional, concise, restrained
|
|
---
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
manager = self._manager()
|
|
manager.invalidate_cache()
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
self.assertTrue(
|
|
any("professional, concise, restrained" in warning for warning in runtime_config.warnings)
|
|
)
|
|
|
|
def test_outdated_system_tasks_definition_falls_back_to_bundled_defaults(self):
|
|
self.agent_root.mkdir(parents=True, exist_ok=True)
|
|
runtime_root = self.agent_root / "runtime"
|
|
shutil.copytree(self.bundled_root, runtime_root)
|
|
system_tasks = runtime_root / "system_tasks" / "SYSTEM_TASKS.md"
|
|
system_tasks.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
---
|
|
version: 1
|
|
shared_rules:
|
|
- legacy system tasks
|
|
task_types:
|
|
heartbeat:
|
|
header: "[Legacy Heartbeat]"
|
|
objective: "legacy"
|
|
---
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
manager = self._manager()
|
|
manager.invalidate_cache()
|
|
runtime_config = manager.load_runtime_config()
|
|
|
|
self.assertTrue(runtime_config.used_fallback)
|
|
self.assertEqual(runtime_config.system_tasks.version, 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|