diff --git a/astrbot/core/computer/booters/shipyard_neo.py b/astrbot/core/computer/booters/shipyard_neo.py index a1a8ad55a3..dd982960f4 100644 --- a/astrbot/core/computer/booters/shipyard_neo.py +++ b/astrbot/core/computer/booters/shipyard_neo.py @@ -353,12 +353,12 @@ def __init__( self, endpoint_url: str, access_token: str, - profile: str = DEFAULT_PROFILE, + profile: str = "", ttl: int = 3600, ) -> None: self._endpoint_url = endpoint_url self._access_token = access_token - self._profile = profile + self._profile = profile.strip() if profile else "" self._ttl = ttl self._client: BayClient | None = None self._sandbox: Sandbox | None = None @@ -431,7 +431,9 @@ async def boot(self, session_id: str) -> None: ) await self._client.__aenter__() - # Resolve profile: user-specified > smart selection > default + # Resolve profile: user-specified > smart selection > default. + # An empty profile means auto-select; any non-empty profile must be + # honoured as an explicit choice, including "python-default". resolved_profile = await self._resolve_profile(self._client) self._sandbox = await self._client.create_sandbox( @@ -535,7 +537,7 @@ async def _resolve_profile(self, client: Any) -> str: """Pick the best profile for this session. Resolution order: - 1. User-specified profile (non-empty, non-default) → use as-is. + 1. User-specified profile (non-empty) → use as-is. 2. Query ``GET /v1/profiles`` and pick the profile with the most capabilities, preferring profiles that include ``"browser"``. 3. Fall back to :attr:`DEFAULT_PROFILE`. @@ -544,8 +546,8 @@ async def _resolve_profile(self, client: Any) -> str: misconfigured token, and silently falling back would just delay the real failure to ``create_sandbox``. """ - # User explicitly set a profile → honour it - if self._profile and self._profile != self.DEFAULT_PROFILE: + # User explicitly set a profile → honour it. + if self._profile: logger.info("[Computer] Using user-specified profile: %s", self._profile) return self._profile diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 3e5dee89c8..c7e8c255d8 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -3315,7 +3315,7 @@ "provider_settings.sandbox.shipyard_neo_profile": { "description": "Shipyard Neo Profile", "type": "string", - "hint": "Shipyard Neo 沙箱 profile,如 python-default。", + "hint": "Shipyard Neo 沙箱 profile,如 python-default。留空时自动选择能力更完整的 profile。", "condition": { "provider_settings.computer_use_runtime": "sandbox", "provider_settings.sandbox.booter": "shipyard_neo", diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index bfb46ab6a0..534358439b 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -180,7 +180,7 @@ }, "shipyard_neo_profile": { "description": "Shipyard Neo Profile", - "hint": "Sandbox profile for Shipyard Neo, e.g. python-default." + "hint": "Sandbox profile for Shipyard Neo, e.g. python-default. Leave empty to auto-select the most capable profile." }, "shipyard_neo_ttl": { "description": "Shipyard Neo Sandbox TTL", diff --git a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json index 9f593fc503..d196cb37a3 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json +++ b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json @@ -180,7 +180,7 @@ }, "shipyard_neo_profile": { "description": "Профиль Shipyard Neo", - "hint": "Профиль песочницы, например, python-default." + "hint": "Профиль песочницы, например, python-default. Оставьте пустым для автоматического выбора самого функционального профиля." }, "shipyard_neo_ttl": { "description": "TTL песочницы Shipyard Neo", diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index 59c0104de2..c8d9d572af 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -182,7 +182,7 @@ }, "shipyard_neo_profile": { "description": "Shipyard Neo Profile", - "hint": "Shipyard Neo 沙箱 profile,例如 python-default。" + "hint": "Shipyard Neo 沙箱 profile,例如 python-default。留空时自动选择能力更完整的 profile。" }, "shipyard_neo_ttl": { "description": "Shipyard Neo Sandbox 存活时间(秒)", diff --git a/docs/en/use/astrbot-agent-sandbox.md b/docs/en/use/astrbot-agent-sandbox.md index 775418c27a..31115d20de 100644 --- a/docs/en/use/astrbot-agent-sandbox.md +++ b/docs/en/use/astrbot-agent-sandbox.md @@ -328,7 +328,7 @@ If you choose `Shipyard Neo`, the main configuration items are: - If AstrBot can access Bay's `credentials.json`, you may leave it empty and let AstrBot auto-discover it - `Shipyard Neo Profile` - For example `python-default` or `browser-python` - - If not explicitly specified, AstrBot will try to choose a profile with richer capabilities, preferring one that includes the `browser` capability, and fall back to `python-default` if needed + - If left empty, AstrBot will try to choose a profile with richer capabilities, preferring one that includes the `browser` capability, and fall back to `python-default` if needed - `Shipyard Neo Sandbox TTL` - The upper lifetime limit of the sandbox, defaulting to 3600 seconds (1 hour) diff --git a/docs/zh/use/astrbot-agent-sandbox.md b/docs/zh/use/astrbot-agent-sandbox.md index ff59c6a7bd..fd80691411 100644 --- a/docs/zh/use/astrbot-agent-sandbox.md +++ b/docs/zh/use/astrbot-agent-sandbox.md @@ -434,7 +434,7 @@ docker pull soulter/shipyard-ship:latest - 如果是官方联合部署,且 AstrBot 能访问 Bay 的 `credentials.json`,可以留空自动发现 - `Shipyard Neo Profile` - 例如 `python-default`、`browser-python` - - 如果未手动指定,AstrBot 会优先尝试选择能力更完整、且优先带有 `browser` capability 的 profile,失败时再回退到 `python-default` + - 如果留空,AstrBot 会优先尝试选择能力更完整、且优先带有 `browser` capability 的 profile,失败时再回退到 `python-default` - `Shipyard Neo Sandbox TTL` - sandbox 生命周期上限,默认值为 3600 秒(1 小时) diff --git a/tests/test_profile_aware_tools.py b/tests/test_profile_aware_tools.py index e8c2954380..86468c3451 100644 --- a/tests/test_profile_aware_tools.py +++ b/tests/test_profile_aware_tools.py @@ -169,7 +169,7 @@ def test_skill_tools_always_registered(self): class TestResolveProfile: """Test smart profile selection logic.""" - def _make_booter(self, profile: str = "python-default"): + def _make_booter(self, profile: str = ""): from astrbot.core.computer.booters.shipyard_neo import ShipyardNeoBooter return ShipyardNeoBooter( @@ -186,9 +186,17 @@ async def test_user_specified_profile_honoured(self): result = await booter._resolve_profile(client) assert result == "browser-python" + @pytest.mark.asyncio + async def test_user_specified_default_profile_honoured(self): + """User explicitly sets python-default → use it directly.""" + booter = self._make_booter(profile="python-default") + client = SimpleNamespace() # list_profiles should NOT be called + result = await booter._resolve_profile(client) + assert result == "python-default" + @pytest.mark.asyncio async def test_selects_browser_profile(self): - """When multiple profiles available, prefer one with browser.""" + """When profile is empty, prefer an available profile with browser.""" async def _mock_list_profiles(): return SimpleNamespace(