Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions astrbot/core/computer/booters/shipyard_neo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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`.
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
},
"shipyard_neo_profile": {
"description": "Профиль Shipyard Neo",
"hint": "Профиль песочницы, например, python-default."
"hint": "Профиль песочницы, например, python-default. Оставьте пустым для автоматического выбора самого функционального профиля."
},
"shipyard_neo_ttl": {
"description": "TTL песочницы Shipyard Neo",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 存活时间(秒)",
Expand Down
2 changes: 1 addition & 1 deletion docs/en/use/astrbot-agent-sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/use/astrbot-agent-sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 小时)

Expand Down
12 changes: 10 additions & 2 deletions tests/test_profile_aware_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down
Loading