-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: support anthropic skills #4715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
71ffe52
feat: support anthropic skills
Soulter 4319f7e
chore: ruff
Soulter 8bb414c
feat: implement skills management and selection in persona configuration
Soulter 51db53d
feat: enhance skills management with local environment tools and perm…
Soulter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import os | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| from dataclasses import dataclass | ||
| from typing import Any | ||
|
|
||
| from astrbot.api import logger | ||
| from astrbot.core.utils.astrbot_path import ( | ||
| get_astrbot_data_path, | ||
| get_astrbot_root, | ||
| get_astrbot_temp_path, | ||
| ) | ||
|
|
||
| from ..olayer import FileSystemComponent, PythonComponent, ShellComponent | ||
| from .base import ComputerBooter | ||
|
|
||
| _BLOCKED_COMMAND_PATTERNS = [ | ||
| " rm -rf ", | ||
| " rm -fr ", | ||
| " rm -r ", | ||
| " mkfs", | ||
| " dd if=", | ||
| " shutdown", | ||
| " reboot", | ||
| " poweroff", | ||
| " halt", | ||
| " sudo ", | ||
| ":(){:|:&};:", | ||
| " kill -9 ", | ||
| " killall ", | ||
| ] | ||
|
|
||
|
|
||
| def _is_safe_command(command: str) -> bool: | ||
| cmd = f" {command.strip().lower()} " | ||
| return not any(pat in cmd for pat in _BLOCKED_COMMAND_PATTERNS) | ||
|
|
||
|
|
||
| def _ensure_safe_path(path: str) -> str: | ||
| abs_path = os.path.abspath(path) | ||
| allowed_roots = [ | ||
| os.path.abspath(get_astrbot_root()), | ||
| os.path.abspath(get_astrbot_data_path()), | ||
| os.path.abspath(get_astrbot_temp_path()), | ||
| ] | ||
| if not any(abs_path.startswith(root) for root in allowed_roots): | ||
| raise PermissionError("Path is outside the allowed computer roots.") | ||
| return abs_path | ||
|
|
||
|
|
||
| @dataclass | ||
| class LocalShellComponent(ShellComponent): | ||
| async def exec( | ||
| self, | ||
| command: str, | ||
| cwd: str | None = None, | ||
| env: dict[str, str] | None = None, | ||
| timeout: int | None = 30, | ||
| shell: bool = True, | ||
| background: bool = False, | ||
| ) -> dict[str, Any]: | ||
| if not _is_safe_command(command): | ||
| raise PermissionError("Blocked unsafe shell command.") | ||
|
|
||
| def _run() -> dict[str, Any]: | ||
| run_env = os.environ.copy() | ||
| if env: | ||
| run_env.update({str(k): str(v) for k, v in env.items()}) | ||
| working_dir = _ensure_safe_path(cwd) if cwd else get_astrbot_root() | ||
| if background: | ||
| proc = subprocess.Popen( | ||
| command, | ||
| shell=shell, | ||
| cwd=working_dir, | ||
| env=run_env, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| text=True, | ||
| ) | ||
| return {"pid": proc.pid, "stdout": "", "stderr": "", "exit_code": None} | ||
| result = subprocess.run( | ||
| command, | ||
| shell=shell, | ||
| cwd=working_dir, | ||
| env=run_env, | ||
| timeout=timeout, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
sourcery-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return { | ||
| "stdout": result.stdout, | ||
| "stderr": result.stderr, | ||
| "exit_code": result.returncode, | ||
| } | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
|
|
||
| @dataclass | ||
| class LocalPythonComponent(PythonComponent): | ||
| async def exec( | ||
| self, | ||
| code: str, | ||
| kernel_id: str | None = None, | ||
| timeout: int = 30, | ||
| silent: bool = False, | ||
| ) -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| try: | ||
| result = subprocess.run( | ||
| [os.environ.get("PYTHON", sys.executable), "-c", code], | ||
sourcery-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| timeout=timeout, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
sourcery-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| stdout = "" if silent else result.stdout | ||
| stderr = result.stderr if result.returncode != 0 else "" | ||
| return { | ||
| "data": { | ||
| "output": {"text": stdout, "images": []}, | ||
| "error": stderr, | ||
| } | ||
| } | ||
| except subprocess.TimeoutExpired: | ||
| return { | ||
| "data": { | ||
| "output": {"text": "", "images": []}, | ||
| "error": "Execution timed out.", | ||
| } | ||
| } | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
|
|
||
| @dataclass | ||
| class LocalFileSystemComponent(FileSystemComponent): | ||
| async def create_file( | ||
| self, path: str, content: str = "", mode: int = 0o644 | ||
| ) -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| abs_path = _ensure_safe_path(path) | ||
| os.makedirs(os.path.dirname(abs_path), exist_ok=True) | ||
| with open(abs_path, "w", encoding="utf-8") as f: | ||
| f.write(content) | ||
| os.chmod(abs_path, mode) | ||
| return {"success": True, "path": abs_path} | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
| async def read_file(self, path: str, encoding: str = "utf-8") -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| abs_path = _ensure_safe_path(path) | ||
| with open(abs_path, encoding=encoding) as f: | ||
| content = f.read() | ||
| return {"success": True, "content": content} | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
| async def write_file( | ||
| self, path: str, content: str, mode: str = "w", encoding: str = "utf-8" | ||
| ) -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| abs_path = _ensure_safe_path(path) | ||
| os.makedirs(os.path.dirname(abs_path), exist_ok=True) | ||
| with open(abs_path, mode, encoding=encoding) as f: | ||
| f.write(content) | ||
| return {"success": True, "path": abs_path} | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
| async def delete_file(self, path: str) -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| abs_path = _ensure_safe_path(path) | ||
| if os.path.isdir(abs_path): | ||
| shutil.rmtree(abs_path) | ||
| else: | ||
| os.remove(abs_path) | ||
| return {"success": True, "path": abs_path} | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
| async def list_dir( | ||
| self, path: str = ".", show_hidden: bool = False | ||
| ) -> dict[str, Any]: | ||
| def _run() -> dict[str, Any]: | ||
| abs_path = _ensure_safe_path(path) | ||
| entries = os.listdir(abs_path) | ||
| if not show_hidden: | ||
| entries = [e for e in entries if not e.startswith(".")] | ||
| return {"success": True, "entries": entries} | ||
|
|
||
| return await asyncio.to_thread(_run) | ||
|
|
||
|
|
||
| class LocalBooter(ComputerBooter): | ||
| def __init__(self) -> None: | ||
| self._fs = LocalFileSystemComponent() | ||
| self._python = LocalPythonComponent() | ||
| self._shell = LocalShellComponent() | ||
|
|
||
| async def boot(self, session_id: str) -> None: | ||
| logger.info(f"Local computer booter initialized for session: {session_id}") | ||
|
|
||
| async def shutdown(self) -> None: | ||
| logger.info("Local computer booter shutdown complete.") | ||
|
|
||
| @property | ||
| def fs(self) -> FileSystemComponent: | ||
| return self._fs | ||
|
|
||
| @property | ||
| def python(self) -> PythonComponent: | ||
| return self._python | ||
|
|
||
| @property | ||
| def shell(self) -> ShellComponent: | ||
| return self._shell | ||
|
|
||
| async def upload_file(self, path: str, file_name: str) -> dict: | ||
| raise NotImplementedError( | ||
| "LocalBooter does not support upload_file operation. Use shell instead." | ||
| ) | ||
|
|
||
| async def download_file(self, remote_path: str, local_path: str): | ||
| raise NotImplementedError( | ||
| "LocalBooter does not support download_file operation. Use shell instead." | ||
| ) | ||
|
|
||
| async def available(self) -> bool: | ||
| return True | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.