Skip to content

Commit 2b5d86b

Browse files
authored
fix: honor computer_use_require_admin in shipyard_neo tools (#6951)
1 parent b2718b0 commit 2b5d86b

4 files changed

Lines changed: 120 additions & 22 deletions

File tree

astrbot/core/computer/tools/browser.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,13 @@
88
from astrbot.core.astr_agent_context import AstrAgentContext
99

1010
from ..computer_client import get_booter
11+
from .permissions import check_admin_permission
1112

1213

1314
def _to_json(data: Any) -> str:
1415
return json.dumps(data, ensure_ascii=False, default=str)
1516

1617

17-
def _ensure_admin(context: ContextWrapper[AstrAgentContext]) -> str | None:
18-
if context.context.event.role != "admin":
19-
return (
20-
"error: Permission denied. Browser and skill lifecycle tools are only allowed "
21-
"for admin users."
22-
)
23-
return None
24-
25-
2618
async def _get_browser_component(context: ContextWrapper[AstrAgentContext]) -> Any:
2719
booter = await get_booter(
2820
context.context.context,
@@ -77,7 +69,7 @@ async def call(
7769
learn: bool = False,
7870
include_trace: bool = False,
7971
) -> ToolExecResult:
80-
if err := _ensure_admin(context):
72+
if err := check_admin_permission(context, "Using browser tools"):
8173
return err
8274
try:
8375
browser = await _get_browser_component(context)
@@ -140,7 +132,7 @@ async def call(
140132
learn: bool = False,
141133
include_trace: bool = False,
142134
) -> ToolExecResult:
143-
if err := _ensure_admin(context):
135+
if err := check_admin_permission(context, "Using browser tools"):
144136
return err
145137
try:
146138
browser = await _get_browser_component(context)
@@ -187,7 +179,7 @@ async def call(
187179
description: str | None = None,
188180
tags: str | None = None,
189181
) -> ToolExecResult:
190-
if err := _ensure_admin(context):
182+
if err := check_admin_permission(context, "Using browser tools"):
191183
return err
192184
try:
193185
browser = await _get_browser_component(context)

astrbot/core/computer/tools/neo_skills.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from astrbot.core.skills.neo_skill_sync import NeoSkillSyncManager
1111

1212
from ..computer_client import get_booter
13+
from .permissions import check_admin_permission
1314

1415

1516
def _to_jsonable(model_like: Any) -> Any:
@@ -26,12 +27,6 @@ def _to_json_text(data: Any) -> str:
2627
return json.dumps(_to_jsonable(data), ensure_ascii=False, default=str)
2728

2829

29-
def _ensure_admin(context: ContextWrapper[AstrAgentContext]) -> str | None:
30-
if context.context.event.role != "admin":
31-
return "error: Permission denied. Skill lifecycle tools are only allowed for admin users."
32-
return None
33-
34-
3530
async def _get_neo_context(
3631
context: ContextWrapper[AstrAgentContext],
3732
) -> tuple[Any, Any]:
@@ -59,7 +54,7 @@ async def _run(
5954
neo_call: Callable[[Any, Any], Awaitable[Any]],
6055
error_action: str,
6156
) -> ToolExecResult:
62-
if err := _ensure_admin(context):
57+
if err := check_admin_permission(context, "Using skill lifecycle tools"):
6358
return err
6459
try:
6560
client, sandbox = await _get_neo_context(context)
@@ -392,7 +387,7 @@ async def call(
392387
stage: str = "canary",
393388
sync_to_local: bool = True,
394389
) -> ToolExecResult:
395-
if err := _ensure_admin(context):
390+
if err := check_admin_permission(context, "Using skill lifecycle tools"):
396391
return err
397392
if stage not in {"canary", "stable"}:
398393
return "Error promoting skill candidate: stage must be canary or stable."
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import json
2+
from types import SimpleNamespace
3+
4+
import pytest
5+
6+
from astrbot.core.agent.run_context import ContextWrapper
7+
from astrbot.core.computer.tools.browser import BrowserExecTool
8+
from astrbot.core.computer.tools.neo_skills import GetExecutionHistoryTool
9+
10+
11+
class _FakeBrowser:
12+
async def exec(self, **kwargs):
13+
return {
14+
"ok": True,
15+
"cmd": kwargs["cmd"],
16+
}
17+
18+
19+
class _FakeSandbox:
20+
async def get_execution_history(self, **kwargs):
21+
return {
22+
"items": [],
23+
"limit": kwargs["limit"],
24+
}
25+
26+
27+
def _make_run_context(require_admin: bool, role: str = "member") -> ContextWrapper:
28+
config_holder = SimpleNamespace(
29+
get_config=lambda umo: { # noqa: ARG005
30+
"provider_settings": {
31+
"computer_use_require_admin": require_admin,
32+
}
33+
}
34+
)
35+
event = SimpleNamespace(
36+
role=role,
37+
unified_msg_origin="qq_official:friend:user-1",
38+
get_sender_id=lambda: "user-1",
39+
)
40+
astr_ctx = SimpleNamespace(context=config_holder, event=event)
41+
return ContextWrapper(context=astr_ctx)
42+
43+
44+
@pytest.mark.asyncio
45+
async def test_browser_tool_allows_non_admin_when_admin_requirement_disabled(
46+
monkeypatch,
47+
):
48+
async def _fake_get_booter(_ctx, _session_id):
49+
return SimpleNamespace(browser=_FakeBrowser())
50+
51+
monkeypatch.setattr(
52+
"astrbot.core.computer.tools.browser.get_booter",
53+
_fake_get_booter,
54+
)
55+
56+
result = await BrowserExecTool().call(
57+
_make_run_context(require_admin=False),
58+
cmd="open https://example.com",
59+
)
60+
61+
assert json.loads(result)["ok"] is True
62+
63+
64+
@pytest.mark.asyncio
65+
async def test_neo_skill_tool_allows_non_admin_when_admin_requirement_disabled(
66+
monkeypatch,
67+
):
68+
async def _fake_get_booter(_ctx, _session_id):
69+
return SimpleNamespace(
70+
bay_client=object(),
71+
sandbox=_FakeSandbox(),
72+
)
73+
74+
monkeypatch.setattr(
75+
"astrbot.core.computer.tools.neo_skills.get_booter",
76+
_fake_get_booter,
77+
)
78+
79+
result = await GetExecutionHistoryTool().call(
80+
_make_run_context(require_admin=False),
81+
limit=5,
82+
)
83+
84+
payload = json.loads(result)
85+
assert payload["items"] == []
86+
assert payload["limit"] == 5
87+
88+
89+
@pytest.mark.asyncio
90+
async def test_browser_tool_still_denies_non_admin_when_admin_requirement_enabled():
91+
result = await BrowserExecTool().call(
92+
_make_run_context(require_admin=True),
93+
cmd="open https://example.com",
94+
)
95+
96+
assert "Permission denied" in result
97+
assert "Using browser tools is only allowed for admin users" in result
98+
assert "User's ID is: user-1" in result

tests/test_neo_skill_tools.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,21 @@ async def _fake_sync_release(self, client, **kwargs):
5454
_fake_sync_release,
5555
)
5656

57-
event = SimpleNamespace(role="admin", unified_msg_origin="session-1")
58-
astr_ctx = SimpleNamespace(context=SimpleNamespace(), event=event)
57+
event = SimpleNamespace(
58+
role="admin",
59+
unified_msg_origin="session-1",
60+
get_sender_id=lambda: "admin-user",
61+
)
62+
astr_ctx = SimpleNamespace(
63+
context=SimpleNamespace(
64+
get_config=lambda umo: { # noqa: ARG005
65+
"provider_settings": {
66+
"computer_use_require_admin": True,
67+
}
68+
}
69+
),
70+
event=event,
71+
)
5972
run_ctx = ContextWrapper(context=astr_ctx)
6073

6174
tool = PromoteSkillCandidateTool()

0 commit comments

Comments
 (0)