perf: shell executed in workspace dir in local env

This commit is contained in:
Soulter
2026-04-10 15:38:40 +08:00
parent 5f049f2bb5
commit 7bf1d19332
8 changed files with 62 additions and 43 deletions

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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 = (

View File

@@ -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 = {

View File

@@ -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)}"

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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(