from __future__ import annotations import asyncio from pathlib import Path from astrbot.core.computer import computer_client class _FakeShell: def __init__(self, sync_payload_json: str): self.sync_payload_json = sync_payload_json self.commands: list[str] = [] async def exec(self, command: str, **kwargs): _ = kwargs self.commands.append(command) if "PYBIN" in command and "managed_skills" in command: return { "success": True, "stdout": self.sync_payload_json, "stderr": "", "exit_code": 0, } return {"success": True, "stdout": "", "stderr": "", "exit_code": 0} class _FakeBooter: def __init__(self, sync_payload_json: str): self.shell = _FakeShell(sync_payload_json) self.uploads: list[tuple[str, str]] = [] async def upload_file(self, path: str, file_name: str) -> dict: self.uploads.append((path, file_name)) return {"success": True} def test_sync_skills_keeps_builtin_skills_when_local_is_empty(monkeypatch, tmp_path: Path): skills_root = tmp_path / "skills" temp_root = tmp_path / "temp" skills_root.mkdir(parents=True, exist_ok=True) temp_root.mkdir(parents=True, exist_ok=True) captured = {"skills": None} def _fake_set_cache(self, skills): captured["skills"] = skills monkeypatch.setattr( "astrbot.core.computer.computer_client.get_astrbot_skills_path", lambda: str(skills_root), ) monkeypatch.setattr( "astrbot.core.computer.computer_client.get_astrbot_temp_path", lambda: str(temp_root), ) monkeypatch.setattr( "astrbot.core.computer.computer_client.SkillManager.set_sandbox_skills_cache", _fake_set_cache, ) booter = _FakeBooter( '{"skills":[{"name":"python-sandbox","description":"ship","path":"skills/python-sandbox/SKILL.md"}]}' ) asyncio.run(computer_client._sync_skills_to_sandbox(booter)) assert booter.uploads == [] assert any(cmd == "rm -f skills/skills.zip" for cmd in booter.shell.commands) assert captured["skills"] == [ { "name": "python-sandbox", "description": "ship", "path": "skills/python-sandbox/SKILL.md", } ] def test_sync_skills_uses_managed_strategy_instead_of_wiping_all( monkeypatch, tmp_path: Path, ): skills_root = tmp_path / "skills" temp_root = tmp_path / "temp" skill_dir = skills_root / "custom-agent-skill" skill_dir.mkdir(parents=True, exist_ok=True) skill_dir.joinpath("SKILL.md").write_text("# demo", encoding="utf-8") temp_root.mkdir(parents=True, exist_ok=True) captured = {"skills": None} def _fake_set_cache(self, skills): captured["skills"] = skills monkeypatch.setattr( "astrbot.core.computer.computer_client.get_astrbot_skills_path", lambda: str(skills_root), ) monkeypatch.setattr( "astrbot.core.computer.computer_client.get_astrbot_temp_path", lambda: str(temp_root), ) monkeypatch.setattr( "astrbot.core.computer.computer_client.SkillManager.set_sandbox_skills_cache", _fake_set_cache, ) booter = _FakeBooter( '{"skills":[{"name":"custom-agent-skill","description":"","path":"skills/custom-agent-skill/SKILL.md"}]}' ) asyncio.run(computer_client._sync_skills_to_sandbox(booter)) assert len(booter.uploads) == 1 assert booter.uploads[0][1] == "skills/skills.zip" assert not any( "find skills -mindepth 1 -delete" in cmd for cmd in booter.shell.commands ) assert captured["skills"] == [ { "name": "custom-agent-skill", "description": "", "path": "skills/custom-agent-skill/SKILL.md", } ]