diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index bf8bd12b5..7e4b5379e 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -65,7 +65,7 @@ from astrbot.core.tools.computer_tools import ( RollbackSkillReleaseTool, RunBrowserSkillTool, SyncSkillReleaseTool, - _normalize_umo_for_workspace, + normalize_umo_for_workspace, ) from astrbot.core.tools.cron_tools import ( CreateActiveCronTool, @@ -304,7 +304,7 @@ def _apply_prompt_prefix(req: ProviderRequest, cfg: dict) -> None: def _get_workspace_path_for_umo(umo: str) -> Path: - normalized_umo = _normalize_umo_for_workspace(umo) + normalized_umo = normalize_umo_for_workspace(umo) return Path(get_astrbot_workspaces_path()) / normalized_umo diff --git a/astrbot/core/tools/computer_tools/__init__.py b/astrbot/core/tools/computer_tools/__init__.py index a147ccbcd..7e364ffd2 100644 --- a/astrbot/core/tools/computer_tools/__init__.py +++ b/astrbot/core/tools/computer_tools/__init__.py @@ -5,9 +5,7 @@ from .fs import ( FileUploadTool, FileWriteTool, GrepTool, - _normalize_umo_for_workspace, ) -from .permissions import check_admin_permission from .python import LocalPythonTool, PythonTool from .shell import ExecuteShellTool from .shipyard_neo import ( @@ -26,6 +24,7 @@ from .shipyard_neo import ( RunBrowserSkillTool, SyncSkillReleaseTool, ) +from .util import check_admin_permission, normalize_umo_for_workspace __all__ = [ "AnnotateExecutionTool", @@ -51,6 +50,6 @@ __all__ = [ "RollbackSkillReleaseTool", "RunBrowserSkillTool", "SyncSkillReleaseTool", - "_normalize_umo_for_workspace", + "normalize_umo_for_workspace", "check_admin_permission", ] diff --git a/astrbot/core/tools/computer_tools/fs.py b/astrbot/core/tools/computer_tools/fs.py index 8b513913c..94f987fcd 100644 --- a/astrbot/core/tools/computer_tools/fs.py +++ b/astrbot/core/tools/computer_tools/fs.py @@ -32,7 +32,6 @@ Local path resolution rule: """ import os -import re import uuid from dataclasses import dataclass, field from pathlib import Path @@ -48,11 +47,15 @@ from astrbot.core.message.components import File from astrbot.core.utils.astrbot_path import ( get_astrbot_skills_path, get_astrbot_temp_path, - get_astrbot_workspaces_path, ) from ..registry import builtin_tool -from .permissions import check_admin_permission +from .util import ( + check_admin_permission, + is_local_runtime, + normalize_umo_for_workspace, + workspace_root, +) _COMPUTER_RUNTIME_TOOL_CONFIG = { "provider_settings.computer_use_runtime": ("local", "sandbox"), @@ -62,14 +65,9 @@ _SANDBOX_RUNTIME_TOOL_CONFIG = { } -def _normalize_umo_for_workspace(umo: str) -> str: - normalized = re.sub(r"[^A-Za-z0-9._-]+", "_", umo.strip()) - return normalized or "unknown" - - def _restricted_env_path_labels(umo: str) -> list[str]: """Labels for the allowed directories in a local(not sandbox) and restricted(not admin) environment""" - normalized_umo = _normalize_umo_for_workspace(umo) + normalized_umo = normalize_umo_for_workspace(umo) return [ "data/skills", f"data/workspaces/{normalized_umo}", @@ -77,32 +75,17 @@ def _restricted_env_path_labels(umo: str) -> list[str]: ] -def _workspace_root(umo: str) -> Path: - """Root directory for relative paths in local runtime""" - normalized_umo = _normalize_umo_for_workspace(umo) - return (Path(get_astrbot_workspaces_path()) / normalized_umo).resolve(strict=False) - - def _read_allowed_roots(umo: str) -> tuple[Path, ...]: """Non-admin users can only read files within these directories (and their subdirectories)""" return ( Path(get_astrbot_skills_path()).resolve(strict=False), - _workspace_root(umo), + workspace_root(umo), Path("/tmp/.astrbot").resolve(strict=False), ) -def _is_local_runtime(context: ContextWrapper[AstrAgentContext]) -> bool: - cfg = context.context.context.get_config( - umo=context.context.event.unified_msg_origin - ) - provider_settings = cfg.get("provider_settings", {}) - runtime = str(provider_settings.get("computer_use_runtime", "local")) - return runtime == "local" - - def _is_restricted_env(context: ContextWrapper[AstrAgentContext]) -> bool: - if not _is_local_runtime(context): + if not is_local_runtime(context): return False cfg = context.context.context.get_config( umo=context.context.event.unified_msg_origin @@ -120,7 +103,7 @@ def _resolve_tool_path(path: str, *, local_env: bool, umo: str) -> str: if candidate.is_absolute(): return str(candidate.resolve(strict=False)) if local_env: - return str((_workspace_root(umo) / candidate).resolve(strict=False)) + return str((workspace_root(umo) / candidate).resolve(strict=False)) return normalized_path @@ -129,7 +112,7 @@ def _resolve_user_path(path: str, *, local_env: bool, umo: str) -> Path: if candidate.is_absolute(): return candidate.resolve(strict=False) if local_env: - return (_workspace_root(umo) / candidate).resolve(strict=False) + return (workspace_root(umo) / candidate).resolve(strict=False) return (Path.cwd() / candidate).resolve(strict=False) @@ -216,7 +199,7 @@ class FileReadTool(FunctionTool): offset: int | None = None, limit: int | None = None, ) -> ToolExecResult: - local_env = _is_local_runtime(context) + local_env = is_local_runtime(context) restricted = _is_restricted_env(context) try: normalized_path = ( @@ -278,7 +261,7 @@ class FileWriteTool(FunctionTool): path: str, content: str, ) -> ToolExecResult: - local_env = _is_local_runtime(context) + local_env = is_local_runtime(context) restricted = _is_restricted_env(context) try: normalized_path = ( @@ -356,7 +339,7 @@ class FileEditTool(FunctionTool): replace_all: bool = False, ) -> ToolExecResult: umo = str(context.context.event.unified_msg_origin) - local_env = _is_local_runtime(context) + local_env = is_local_runtime(context) restricted = _is_restricted_env(context) try: normalized_path = ( @@ -522,7 +505,7 @@ class GrepTool(FunctionTool): if restricted: return [str(root) for root in _read_allowed_roots(umo)] if local_env: - return [str(_workspace_root(umo))] + return [str(workspace_root(umo))] return ["."] if restricted: @@ -554,7 +537,7 @@ class GrepTool(FunctionTool): if not normalized_pattern: return "Error: `pattern` must be a non-empty string." - local_env = _is_local_runtime(context) + local_env = is_local_runtime(context) restricted = _is_restricted_env(context) try: search_paths = ( diff --git a/astrbot/core/tools/computer_tools/python.py b/astrbot/core/tools/computer_tools/python.py index 1be2a6e1b..e0bb6c9de 100644 --- a/astrbot/core/tools/computer_tools/python.py +++ b/astrbot/core/tools/computer_tools/python.py @@ -11,7 +11,7 @@ from astrbot.core.computer.computer_client import get_booter, get_local_booter from astrbot.core.message.message_event_result import MessageChain from ..registry import builtin_tool -from .permissions import check_admin_permission +from .util import check_admin_permission _OS_NAME = platform.system() _SANDBOX_PYTHON_TOOL_CONFIG = { diff --git a/astrbot/core/tools/computer_tools/shell.py b/astrbot/core/tools/computer_tools/shell.py index 98343bc1c..2d3997387 100644 --- a/astrbot/core/tools/computer_tools/shell.py +++ b/astrbot/core/tools/computer_tools/shell.py @@ -8,7 +8,7 @@ from astrbot.core.astr_agent_context import AstrAgentContext from astrbot.core.computer.computer_client import get_booter from ..registry import builtin_tool -from .permissions import check_admin_permission +from .util import check_admin_permission, is_local_runtime, workspace_root _COMPUTER_RUNTIME_TOOL_CONFIG = { "provider_settings.computer_use_runtime": ("local", "sandbox"), @@ -59,7 +59,20 @@ class ExecuteShellTool(FunctionTool): context.context.event.unified_msg_origin, ) try: - result = await sb.shell.exec(command, background=background, env=env) + cwd: str | None = None + if is_local_runtime(context): + current_workspace_root = workspace_root( + context.context.event.unified_msg_origin + ) + current_workspace_root.mkdir(parents=True, exist_ok=True) + cwd = str(current_workspace_root) + + result = await sb.shell.exec( + command, + cwd=cwd, + background=background, + env=env, + ) return json.dumps(result) except Exception as e: return f"Error executing command: {str(e)}" diff --git a/astrbot/core/tools/computer_tools/shipyard_neo/browser.py b/astrbot/core/tools/computer_tools/shipyard_neo/browser.py index 72aa6076d..b4b7f4fd0 100644 --- a/astrbot/core/tools/computer_tools/shipyard_neo/browser.py +++ b/astrbot/core/tools/computer_tools/shipyard_neo/browser.py @@ -7,7 +7,7 @@ from astrbot.core.agent.run_context import ContextWrapper from astrbot.core.agent.tool import ToolExecResult from astrbot.core.astr_agent_context import AstrAgentContext from astrbot.core.computer.computer_client import get_booter -from astrbot.core.tools.computer_tools.permissions import check_admin_permission +from astrbot.core.tools.computer_tools.util import check_admin_permission from astrbot.core.tools.registry import builtin_tool _SHIPYARD_NEO_TOOL_CONFIG = { diff --git a/astrbot/core/tools/computer_tools/shipyard_neo/neo_skills.py b/astrbot/core/tools/computer_tools/shipyard_neo/neo_skills.py index edbe1ef6f..e2c4f5909 100644 --- a/astrbot/core/tools/computer_tools/shipyard_neo/neo_skills.py +++ b/astrbot/core/tools/computer_tools/shipyard_neo/neo_skills.py @@ -9,7 +9,7 @@ from astrbot.core.agent.tool import ToolExecResult from astrbot.core.astr_agent_context import AstrAgentContext from astrbot.core.computer.computer_client import get_booter from astrbot.core.skills.neo_skill_sync import NeoSkillSyncManager -from astrbot.core.tools.computer_tools.permissions import check_admin_permission +from astrbot.core.tools.computer_tools.util import check_admin_permission from astrbot.core.tools.registry import builtin_tool _SHIPYARD_NEO_TOOL_CONFIG = { diff --git a/astrbot/core/tools/computer_tools/permissions.py b/astrbot/core/tools/computer_tools/util.py similarity index 52% rename from astrbot/core/tools/computer_tools/permissions.py rename to astrbot/core/tools/computer_tools/util.py index 489f485f9..a3930b4c6 100644 --- a/astrbot/core/tools/computer_tools/permissions.py +++ b/astrbot/core/tools/computer_tools/util.py @@ -1,5 +1,29 @@ +import re +from pathlib import Path + from astrbot.core.agent.run_context import ContextWrapper from astrbot.core.astr_agent_context import AstrAgentContext +from astrbot.core.utils.astrbot_path import get_astrbot_workspaces_path + + +def normalize_umo_for_workspace(umo: str) -> str: + normalized = re.sub(r"[^A-Za-z0-9._-]+", "_", umo.strip()) + return normalized or "unknown" + + +def workspace_root(umo: str) -> Path: + """Root directory for relative paths in local runtime""" + normalized_umo = normalize_umo_for_workspace(umo) + return (Path(get_astrbot_workspaces_path()) / normalized_umo).resolve(strict=False) + + +def is_local_runtime(context: ContextWrapper[AstrAgentContext]) -> bool: + cfg = context.context.context.get_config( + umo=context.context.event.unified_msg_origin + ) + provider_settings = cfg.get("provider_settings", {}) + runtime = str(provider_settings.get("computer_use_runtime", "local")) + return runtime == "local" def check_admin_permission(