|
20 | 20 | from astrbot.core.astr_agent_run_util import AgentRunner |
21 | 21 | from astrbot.core.astr_agent_tool_exec import FunctionToolExecutor |
22 | 22 | from astrbot.core.astr_main_agent_resources import ( |
| 23 | + ANNOTATE_EXECUTION_TOOL, |
| 24 | + BROWSER_BATCH_EXEC_TOOL, |
| 25 | + BROWSER_EXEC_TOOL, |
23 | 26 | CHATUI_SPECIAL_DEFAULT_PERSONA_PROMPT, |
| 27 | + CREATE_SKILL_CANDIDATE_TOOL, |
| 28 | + CREATE_SKILL_PAYLOAD_TOOL, |
| 29 | + EVALUATE_SKILL_CANDIDATE_TOOL, |
24 | 30 | EXECUTE_SHELL_TOOL, |
25 | 31 | FILE_DOWNLOAD_TOOL, |
26 | 32 | FILE_UPLOAD_TOOL, |
| 33 | + GET_EXECUTION_HISTORY_TOOL, |
| 34 | + GET_SKILL_PAYLOAD_TOOL, |
27 | 35 | KNOWLEDGE_BASE_QUERY_TOOL, |
| 36 | + LIST_SKILL_CANDIDATES_TOOL, |
| 37 | + LIST_SKILL_RELEASES_TOOL, |
28 | 38 | LIVE_MODE_SYSTEM_PROMPT, |
29 | 39 | LLM_SAFETY_MODE_SYSTEM_PROMPT, |
30 | 40 | LOCAL_EXECUTE_SHELL_TOOL, |
31 | 41 | LOCAL_PYTHON_TOOL, |
| 42 | + PROMOTE_SKILL_CANDIDATE_TOOL, |
32 | 43 | PYTHON_TOOL, |
| 44 | + ROLLBACK_SKILL_RELEASE_TOOL, |
| 45 | + RUN_BROWSER_SKILL_TOOL, |
33 | 46 | SANDBOX_MODE_PROMPT, |
34 | 47 | SEND_MESSAGE_TO_USER_TOOL, |
| 48 | + SYNC_SKILL_RELEASE_TOOL, |
35 | 49 | TOOL_CALL_PROMPT, |
36 | 50 | TOOL_CALL_PROMPT_SKILLS_LIKE_MODE, |
37 | 51 | retrieve_knowledge_base, |
@@ -832,19 +846,73 @@ def _apply_sandbox_tools( |
832 | 846 | ) -> None: |
833 | 847 | if req.func_tool is None: |
834 | 848 | req.func_tool = ToolSet() |
835 | | - if config.sandbox_cfg.get("booter") == "shipyard": |
| 849 | + booter = config.sandbox_cfg.get("booter", "shipyard_neo") |
| 850 | + if booter == "shipyard": |
836 | 851 | ep = config.sandbox_cfg.get("shipyard_endpoint", "") |
837 | 852 | at = config.sandbox_cfg.get("shipyard_access_token", "") |
838 | 853 | if not ep or not at: |
839 | 854 | logger.error("Shipyard sandbox configuration is incomplete.") |
840 | 855 | return |
841 | 856 | os.environ["SHIPYARD_ENDPOINT"] = ep |
842 | 857 | os.environ["SHIPYARD_ACCESS_TOKEN"] = at |
| 858 | + |
843 | 859 | req.func_tool.add_tool(EXECUTE_SHELL_TOOL) |
844 | 860 | req.func_tool.add_tool(PYTHON_TOOL) |
845 | 861 | req.func_tool.add_tool(FILE_UPLOAD_TOOL) |
846 | 862 | req.func_tool.add_tool(FILE_DOWNLOAD_TOOL) |
847 | | - req.system_prompt = f"{req.system_prompt}\n{SANDBOX_MODE_PROMPT}\n" |
| 863 | + if booter == "shipyard_neo": |
| 864 | + # Neo-specific path rule: filesystem tools operate relative to sandbox |
| 865 | + # workspace root. Do not prepend "/workspace". |
| 866 | + req.system_prompt += ( |
| 867 | + "\n[Shipyard Neo File Path Rule]\n" |
| 868 | + "When using sandbox filesystem tools (upload/download/read/write/list/delete), " |
| 869 | + "always pass paths relative to the sandbox workspace root. " |
| 870 | + "Example: use `baidu_homepage.png` instead of `/workspace/baidu_homepage.png`.\n" |
| 871 | + ) |
| 872 | + |
| 873 | + req.system_prompt += ( |
| 874 | + "\n[Neo Skill Lifecycle Workflow]\n" |
| 875 | + "When user asks to create/update a reusable skill in Neo mode, use lifecycle tools instead of directly writing local skill folders.\n" |
| 876 | + "Preferred sequence:\n" |
| 877 | + "1) Use `astrbot_create_skill_payload` to store canonical payload content and get `payload_ref`.\n" |
| 878 | + "2) Use `astrbot_create_skill_candidate` with `skill_key` + `source_execution_ids` (and optional `payload_ref`) to create a candidate.\n" |
| 879 | + "3) Use `astrbot_promote_skill_candidate` to release: `stage=canary` for trial; `stage=stable` for production.\n" |
| 880 | + "For stable release, set `sync_to_local=true` to sync `payload.skill_markdown` into local `SKILL.md`.\n" |
| 881 | + "Do not treat ad-hoc generated files as reusable Neo skills unless they are captured via payload/candidate/release.\n" |
| 882 | + "To update an existing skill, create a new payload/candidate and promote a new release version; avoid patching old local folders directly.\n" |
| 883 | + ) |
| 884 | + |
| 885 | + # Determine sandbox capabilities from an already-booted session. |
| 886 | + # If no session exists yet (first request), capabilities is None |
| 887 | + # and we register all tools conservatively. |
| 888 | + from astrbot.core.computer.computer_client import session_booter |
| 889 | + |
| 890 | + sandbox_capabilities: list[str] | None = None |
| 891 | + existing_booter = session_booter.get(session_id) |
| 892 | + if existing_booter is not None: |
| 893 | + sandbox_capabilities = getattr(existing_booter, "capabilities", None) |
| 894 | + |
| 895 | + # Browser tools: only register if profile supports browser |
| 896 | + # (or if capabilities are unknown because sandbox hasn't booted yet) |
| 897 | + if sandbox_capabilities is None or "browser" in sandbox_capabilities: |
| 898 | + req.func_tool.add_tool(BROWSER_EXEC_TOOL) |
| 899 | + req.func_tool.add_tool(BROWSER_BATCH_EXEC_TOOL) |
| 900 | + req.func_tool.add_tool(RUN_BROWSER_SKILL_TOOL) |
| 901 | + |
| 902 | + # Neo-specific tools (always available for shipyard_neo) |
| 903 | + req.func_tool.add_tool(GET_EXECUTION_HISTORY_TOOL) |
| 904 | + req.func_tool.add_tool(ANNOTATE_EXECUTION_TOOL) |
| 905 | + req.func_tool.add_tool(CREATE_SKILL_PAYLOAD_TOOL) |
| 906 | + req.func_tool.add_tool(GET_SKILL_PAYLOAD_TOOL) |
| 907 | + req.func_tool.add_tool(CREATE_SKILL_CANDIDATE_TOOL) |
| 908 | + req.func_tool.add_tool(LIST_SKILL_CANDIDATES_TOOL) |
| 909 | + req.func_tool.add_tool(EVALUATE_SKILL_CANDIDATE_TOOL) |
| 910 | + req.func_tool.add_tool(PROMOTE_SKILL_CANDIDATE_TOOL) |
| 911 | + req.func_tool.add_tool(LIST_SKILL_RELEASES_TOOL) |
| 912 | + req.func_tool.add_tool(ROLLBACK_SKILL_RELEASE_TOOL) |
| 913 | + req.func_tool.add_tool(SYNC_SKILL_RELEASE_TOOL) |
| 914 | + |
| 915 | + req.system_prompt = f"{req.system_prompt or ''}\n{SANDBOX_MODE_PROMPT}\n" |
848 | 916 |
|
849 | 917 |
|
850 | 918 | def _proactive_cron_job_tools(req: ProviderRequest) -> None: |
|
0 commit comments