fix(tools): hide register_skill when Cloud sandboxes cannot scan host directories

scan_skill_directory is disabled for Cloud-managed sandboxes, but the
register_skill tool was still advertised to the model whenever a backend was
available, so every call failed closed without a usable explanation. Advertise
register_skill only when the runtime may scan sandbox directories, and keep
activate available in both modes.
This commit is contained in:
Hyu
2026-09-30 18:44:16 +08:00
parent ff3f321120
commit 4fc700b7e0
2 changed files with 44 additions and 9 deletions
@@ -35,10 +35,7 @@ class SkillToolLoader(loader.ToolLoader):
# Check if sandbox backend is available (same check as native tools)
self._sandbox_available = await self._check_sandbox_available()
if self._sandbox_available:
self._tools = [
self._build_activate_skill_tool(),
self._build_register_skill_tool(),
]
self._tools = self._build_skill_tools()
else:
self.ap.logger.info(
'Skill tools (activate/register_skill) are NOT available. '
@@ -54,14 +51,32 @@ class SkillToolLoader(loader.ToolLoader):
if not await self._is_available():
return []
if not self._tools:
self._tools = [
self._build_activate_skill_tool(),
self._build_register_skill_tool(),
]
self._tools = self._build_skill_tools()
return list(self._tools)
def _build_skill_tools(self) -> list[resource_tool.LLMTool]:
tools = [self._build_activate_skill_tool()]
if self._skill_registration_available():
tools.append(self._build_register_skill_tool())
else:
self.ap.logger.info(
'register_skill is NOT available: Cloud sandboxes never scan arbitrary '
'host skill directories. Install skills through the skill store instead.'
)
return tools
def _skill_registration_available(self) -> bool:
"""Host skill scanning is disabled for Cloud-managed sandboxes."""
box_service = getattr(self.ap, 'box_service', None)
return not bool(getattr(box_service, 'managed_admission_required', False))
async def has_tool(self, name: str) -> bool:
return await self._is_available() and name in SKILL_TOOL_NAMES
if not await self._is_available() or name not in SKILL_TOOL_NAMES:
return False
if name == REGISTER_SKILL_TOOL_NAME:
return self._skill_registration_available()
return True
async def _is_available(self) -> bool:
"""Check if skill tools should be available.
@@ -737,3 +737,23 @@ async def test_native_skill_tools_require_runner_box_binding():
with pytest.raises(BoxValidationError, match='Runner must bind a Box'):
await loader.invoke_tool('exec', {'command': 'true'}, _make_query())
ap.box_service.execute_tool.assert_not_awaited()
@pytest.mark.asyncio
async def test_register_skill_hidden_when_cloud_scanning_is_disabled(self):
from langbot.pkg.provider.tools.loaders.skill_authoring import SkillToolLoader
ap = _make_ap()
ap.skill_mgr = _make_skill_manager({'demo': _make_skill_data(name='demo')})
ap.box_service = SimpleNamespace(
available=True,
managed_admission_required=True,
get_backend_status=AsyncMock(return_value={'backend': {'available': True}}),
)
loader = SkillToolLoader(ap)
await loader.initialize()
assert [tool.name for tool in await loader.get_tools()] == ['activate']
assert await loader.has_tool('register_skill') is False
assert await loader.has_tool('activate') is True