From 093b37e04b1f3929805105bb93d65a32e84b9497 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:08:15 +0800 Subject: [PATCH] feat: add computer use runtime config and handling for skills execution (#4831) * feat: add computer use runtime configuration and handling for skills execution * fix: improve user notification for disabled Computer Use feature in skills execution --- astrbot/core/astr_main_agent.py | 33 ++-- astrbot/core/config/default.py | 168 ++++++++---------- .../method/agent_sub_stages/internal.py | 2 + astrbot/dashboard/routes/skills.py | 17 +- .../components/extension/SkillsSection.vue | 10 +- .../src/components/shared/AstrBotConfigV4.vue | 22 ++- .../src/components/shared/PersonaForm.vue | 12 +- .../en-US/features/config-metadata.json | 42 ++--- .../locales/en-US/features/extension.json | 3 +- .../i18n/locales/en-US/features/persona.json | 1 + .../zh-CN/features/config-metadata.json | 47 ++--- .../locales/zh-CN/features/extension.json | 3 +- .../i18n/locales/zh-CN/features/persona.json | 1 + 13 files changed, 182 insertions(+), 179 deletions(-) diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 25d3777eb..940a15e53 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -99,6 +99,8 @@ class MainAgentBuildConfig: """This will inject healthy and safe system prompt into the main agent, to prevent LLM output harmful information""" safety_mode_strategy: str = "system_prompt" + computer_use_runtime: str = "local" + """The runtime for agent computer use: none, local, or sandbox.""" sandbox_cfg: dict = field(default_factory=dict) add_cron_tools: bool = True """This will add cron job management tools to the main agent for proactive cron job execution.""" @@ -247,7 +249,6 @@ def _apply_local_env_tools(req: ProviderRequest) -> None: req.func_tool.add_tool(LOCAL_EXECUTE_SHELL_TOOL) req.func_tool.add_tool(LOCAL_PYTHON_TOOL) - async def _ensure_persona_and_skills( req: ProviderRequest, cfg: dict, @@ -301,21 +302,11 @@ async def _ensure_persona_and_skills( req.system_prompt += CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT # Inject skills prompt - skills_cfg = cfg.get("skills", {}) - sandbox_cfg = cfg.get("sandbox", {}) + runtime = cfg.get("computer_use_runtime", "local") skill_manager = SkillManager() - runtime = skills_cfg.get("runtime", "local") skills = skill_manager.list_skills(active_only=True, runtime=runtime) - if runtime == "sandbox" and not sandbox_cfg.get("enable", False): - logger.warning( - "Skills runtime is set to sandbox, but sandbox mode is disabled, will skip skills prompt injection.", - ) - req.system_prompt += ( - "\n[Background: User added some skills, and skills runtime is set to sandbox, " - "but sandbox mode is disabled. So skills will be unavailable.]\n" - ) - elif skills: + if skills: if persona and persona.get("skills") is not None: if not persona["skills"]: skills = [] @@ -324,12 +315,12 @@ async def _ensure_persona_and_skills( skills = [skill for skill in skills if skill.name in allowed] if skills: req.system_prompt += f"\n{build_skills_prompt(skills)}\n" - - runtime = skills_cfg.get("runtime", "local") - sandbox_enabled = sandbox_cfg.get("enable", False) - if runtime == "local" and not sandbox_enabled: - _apply_local_env_tools(req) - + if runtime == "none": + req.system_prompt += ( + "User has not enabled the Computer Use feature. " + "You cannot use shell or Python to perform skills. " + "If you need to use these capabilities, ask the user to enable Computer Use in the AstrBot WebUI -> Config." + ) tmgr = plugin_context.get_llm_tool_manager() # sub agents integration @@ -922,8 +913,10 @@ async def build_main_agent( if config.llm_safety_mode: _apply_llm_safety_mode(config, req) - if config.sandbox_cfg.get("enable", False): + if config.computer_use_runtime == "sandbox": _apply_sandbox_tools(config, req, req.session_id) + elif config.computer_use_runtime == "local": + _apply_local_env_tools(req) agent_runner = AgentRunner() astr_agent_ctx = AstrAgentContext( diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 1cc672004..f421d2438 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -117,15 +117,14 @@ DEFAULT_CONFIG = { "proactive_capability": { "add_cron_tools": True, }, + "computer_use_runtime": "local", "sandbox": { - "enable": False, "booter": "shipyard", "shipyard_endpoint": "", "shipyard_access_token": "", "shipyard_ttl": 3600, "shipyard_max_sessions": 10, }, - "skills": {"runtime": "sandbox"}, }, # SubAgent orchestrator mode: # - main_enable = False: disabled; main LLM mounts tools normally (persona selection). @@ -2225,17 +2224,6 @@ CONFIG_METADATA_2 = { }, }, }, - "skills": { - "type": "object", - "items": { - "enable": { - "type": "bool", - }, - "runtime": { - "type": "string", - }, - }, - }, "proactive_capability": { "type": "object", "items": { @@ -2516,6 +2504,7 @@ CONFIG_METADATA_3 = { }, "persona": { "description": "人格", + "hint": "", "type": "object", "items": { "provider_settings.default_personality": { @@ -2531,6 +2520,7 @@ CONFIG_METADATA_3 = { }, "knowledgebase": { "description": "知识库", + "hint": "", "type": "object", "items": { "kb_names": { @@ -2563,6 +2553,7 @@ CONFIG_METADATA_3 = { }, "websearch": { "description": "网页搜索", + "hint": "", "type": "object", "items": { "provider_settings.web_search": { @@ -2573,6 +2564,9 @@ CONFIG_METADATA_3 = { "description": "网页搜索提供商", "type": "string", "options": ["default", "tavily", "baidu_ai_search"], + "condition": { + "provider_settings.web_search": True, + }, }, "provider_settings.websearch_tavily_key": { "description": "Tavily API Key", @@ -2581,6 +2575,7 @@ CONFIG_METADATA_3 = { "hint": "可添加多个 Key 进行轮询。", "condition": { "provider_settings.websearch_provider": "tavily", + "provider_settings.web_search": True, }, }, "provider_settings.websearch_baidu_app_builder_key": { @@ -2594,6 +2589,73 @@ CONFIG_METADATA_3 = { "provider_settings.web_search_link": { "description": "显示来源引用", "type": "bool", + "condition": { + "provider_settings.web_search": True, + }, + }, + }, + "condition": { + "provider_settings.agent_runner_type": "local", + "provider_settings.enable": True, + }, + }, + "agent_computer_use": { + "description": "Agent Computer Use", + "hint": "", + "type": "object", + "items": { + "provider_settings.computer_use_runtime": { + "description": "Computer Use Runtime", + "type": "string", + "options": ["none", "local", "sandbox"], + "labels": ["无", "本地", "沙箱"], + "hint": "选择 Computer Use 运行环境。", + }, + "provider_settings.sandbox.booter": { + "description": "沙箱环境驱动器", + "type": "string", + "options": ["shipyard"], + "labels": ["Shipyard"], + "condition": { + "provider_settings.computer_use_runtime": "sandbox", + }, + }, + "provider_settings.sandbox.shipyard_endpoint": { + "description": "Shipyard API Endpoint", + "type": "string", + "hint": "Shipyard 服务的 API 访问地址。", + "condition": { + "provider_settings.computer_use_runtime": "sandbox", + "provider_settings.sandbox.booter": "shipyard", + }, + "_special": "check_shipyard_connection", + }, + "provider_settings.sandbox.shipyard_access_token": { + "description": "Shipyard Access Token", + "type": "string", + "hint": "用于访问 Shipyard 服务的访问令牌。", + "condition": { + "provider_settings.computer_use_runtime": "sandbox", + "provider_settings.sandbox.booter": "shipyard", + }, + }, + "provider_settings.sandbox.shipyard_ttl": { + "description": "Shipyard Session TTL", + "type": "int", + "hint": "Shipyard 会话的生存时间(秒)。", + "condition": { + "provider_settings.computer_use_runtime": "sandbox", + "provider_settings.sandbox.booter": "shipyard", + }, + }, + "provider_settings.sandbox.shipyard_max_sessions": { + "description": "Shipyard Max Sessions", + "type": "int", + "hint": "Shipyard 最大会话数量。", + "condition": { + "provider_settings.computer_use_runtime": "sandbox", + "provider_settings.sandbox.booter": "shipyard", + }, }, }, "condition": { @@ -2631,86 +2693,6 @@ CONFIG_METADATA_3 = { # "provider_settings.enable": True, # }, # }, - "sandbox": { - "description": "Agent 沙箱环境", - "hint": "", - "type": "object", - "items": { - "provider_settings.sandbox.enable": { - "description": "启用沙箱环境", - "type": "bool", - "hint": "启用后,Agent 可以使用沙箱环境中的工具和资源,如 Python 代码执行、Shell 等。", - }, - "provider_settings.sandbox.booter": { - "description": "沙箱环境驱动器", - "type": "string", - "options": ["shipyard"], - "labels": ["Shipyard"], - "condition": { - "provider_settings.sandbox.enable": True, - }, - }, - "provider_settings.sandbox.shipyard_endpoint": { - "description": "Shipyard API Endpoint", - "type": "string", - "hint": "Shipyard 服务的 API 访问地址。", - "condition": { - "provider_settings.sandbox.enable": True, - "provider_settings.sandbox.booter": "shipyard", - }, - "_special": "check_shipyard_connection", - }, - "provider_settings.sandbox.shipyard_access_token": { - "description": "Shipyard Access Token", - "type": "string", - "hint": "用于访问 Shipyard 服务的访问令牌。", - "condition": { - "provider_settings.sandbox.enable": True, - "provider_settings.sandbox.booter": "shipyard", - }, - }, - "provider_settings.sandbox.shipyard_ttl": { - "description": "Shipyard Session TTL", - "type": "int", - "hint": "Shipyard 会话的生存时间(秒)。", - "condition": { - "provider_settings.sandbox.enable": True, - "provider_settings.sandbox.booter": "shipyard", - }, - }, - "provider_settings.sandbox.shipyard_max_sessions": { - "description": "Shipyard Max Sessions", - "type": "int", - "hint": "Shipyard 最大会话数量。", - "condition": { - "provider_settings.sandbox.enable": True, - "provider_settings.sandbox.booter": "shipyard", - }, - }, - }, - "condition": { - "provider_settings.agent_runner_type": "local", - "provider_settings.enable": True, - }, - }, - "skills": { - "description": "Skills", - "type": "object", - "hint": "", - "items": { - "provider_settings.skills.runtime": { - "description": "Skill Runtime", - "type": "string", - "options": ["local", "sandbox"], - "labels": ["本地", "沙箱"], - "hint": "选择 Skills 运行环境。使用沙箱时需先启用沙箱环境。", - }, - }, - "condition": { - "provider_settings.agent_runner_type": "local", - "provider_settings.enable": True, - }, - }, "proactive_capability": { "description": "主动型 Agent", "hint": "https://docs.astrbot.app/use/proactive-agent.html", diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index 2331d07e8..8fa39f8e8 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -92,6 +92,7 @@ class InternalAgentSubStage(Stage): "safety_mode_strategy", "system_prompt" ) + self.computer_use_runtime = settings.get("computer_use_runtime") self.sandbox_cfg = settings.get("sandbox", {}) # Proactive capability configuration @@ -116,6 +117,7 @@ class InternalAgentSubStage(Stage): dequeue_context_length=self.dequeue_context_length, llm_safety_mode=self.llm_safety_mode, safety_mode_strategy=self.safety_mode_strategy, + computer_use_runtime=self.computer_use_runtime, sandbox_cfg=self.sandbox_cfg, add_cron_tools=self.add_cron_tools, provider_settings=settings, diff --git a/astrbot/dashboard/routes/skills.py b/astrbot/dashboard/routes/skills.py index 21e36905b..f392ffafd 100644 --- a/astrbot/dashboard/routes/skills.py +++ b/astrbot/dashboard/routes/skills.py @@ -24,14 +24,23 @@ class SkillsRoute(Route): async def get_skills(self): try: - cfg = self.core_lifecycle.astrbot_config.get("provider_settings", {}).get( - "skills", {} + provider_settings = self.core_lifecycle.astrbot_config.get( + "provider_settings", {} ) - runtime = cfg.get("runtime", "local") + runtime = provider_settings.get("computer_use_runtime", "local") skills = SkillManager().list_skills( active_only=False, runtime=runtime, show_sandbox_path=False ) - return Response().ok([skill.__dict__ for skill in skills]).__dict__ + return ( + Response() + .ok( + { + "skills": [skill.__dict__ for skill in skills], + "computer_use_runtime": runtime, + } + ) + .__dict__ + ) except Exception as e: logger.error(traceback.format_exc()) return Response().error(str(e)).__dict__ diff --git a/dashboard/src/components/extension/SkillsSection.vue b/dashboard/src/components/extension/SkillsSection.vue index 75d92fffb..e6d3f1eac 100644 --- a/dashboard/src/components/extension/SkillsSection.vue +++ b/dashboard/src/components/extension/SkillsSection.vue @@ -110,7 +110,15 @@ export default { loading.value = true; try { const res = await axios.get("/api/skills"); - skills.value = res.data.data || []; + const payload = res.data?.data || []; + if (Array.isArray(payload)) { + skills.value = payload; + } else { + skills.value = payload.skills || []; + if (payload.computer_use_runtime === "none") { + showMessage(tm("skills.runtimeNoneWarning"), "warning"); + } + } } catch (err) { showMessage(tm("skills.loadFailed"), "error"); } finally { diff --git a/dashboard/src/components/shared/AstrBotConfigV4.vue b/dashboard/src/components/shared/AstrBotConfigV4.vue index a0bbd87bd..15339980a 100644 --- a/dashboard/src/components/shared/AstrBotConfigV4.vue +++ b/dashboard/src/components/shared/AstrBotConfigV4.vue @@ -1,4 +1,5 @@