mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 01:46:07 +00:00
fix(agent-runner): close sandbox skill authoring flow
This commit is contained in:
@@ -440,6 +440,48 @@ class TestSkillToolLoader:
|
||||
assert result['skill_name'] == 'cloned-skill'
|
||||
assert result['source_path'] == '/workspace/repo'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_registered_skill_can_be_activated_in_same_query(self):
|
||||
from langbot.pkg.provider.tools.loaders.skill import PIPELINE_BOUND_SKILLS_KEY
|
||||
from langbot.pkg.provider.tools.loaders.skill_authoring import (
|
||||
ACTIVATE_SKILL_TOOL_NAME,
|
||||
REGISTER_SKILL_TOOL_NAME,
|
||||
SkillToolLoader,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
repo_dir = os.path.join(tmpdir, 'repo')
|
||||
os.makedirs(repo_dir)
|
||||
created = _make_skill_data(name='cloned-skill', package_root=os.path.realpath(repo_dir))
|
||||
ap = _make_ap()
|
||||
ap.box_service = SimpleNamespace(default_workspace=tmpdir, available=True)
|
||||
ap.skill_mgr = SimpleNamespace(skills={'existing': _make_skill_data(name='existing')})
|
||||
|
||||
async def create_skill(_data):
|
||||
ap.skill_mgr.skills['cloned-skill'] = created
|
||||
return created
|
||||
|
||||
ap.skill_service = SimpleNamespace(
|
||||
scan_directory_async=AsyncMock(return_value=created),
|
||||
create_skill=AsyncMock(side_effect=create_skill),
|
||||
)
|
||||
query = SimpleNamespace(variables={PIPELINE_BOUND_SKILLS_KEY: ['existing']})
|
||||
loader = SkillToolLoader(ap)
|
||||
|
||||
await loader.invoke_tool(
|
||||
REGISTER_SKILL_TOOL_NAME,
|
||||
{'path': '/workspace/repo', 'name': 'cloned-skill'},
|
||||
query,
|
||||
)
|
||||
activated = await loader.invoke_tool(
|
||||
ACTIVATE_SKILL_TOOL_NAME,
|
||||
{'skill_name': 'cloned-skill'},
|
||||
query,
|
||||
)
|
||||
|
||||
assert query.variables[PIPELINE_BOUND_SKILLS_KEY] == ['existing', 'cloned-skill']
|
||||
assert activated['activated'] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_skill_rejects_workspace_escape(self):
|
||||
from langbot.pkg.provider.tools.loaders.skill_authoring import (
|
||||
|
||||
@@ -4,7 +4,7 @@ import base64
|
||||
import os
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -236,6 +236,32 @@ async def test_write_creates_subdirectories():
|
||||
assert f.read() == 'nested'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_falls_back_to_box_for_container_owned_workspace_file():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
box_service = SimpleNamespace(
|
||||
available=True,
|
||||
default_workspace=tmpdir,
|
||||
execute_tool=AsyncMock(
|
||||
return_value={
|
||||
'ok': True,
|
||||
'stdout': '{"ok": true, "path": "/workspace/root-owned.txt"}',
|
||||
}
|
||||
),
|
||||
)
|
||||
loader = NativeToolLoader(SimpleNamespace(box_service=box_service, logger=Mock()))
|
||||
loader._write_host_file = Mock(side_effect=PermissionError)
|
||||
|
||||
result = await loader.invoke_tool(
|
||||
'write',
|
||||
{'path': '/workspace/root-owned.txt', 'content': 'updated'},
|
||||
_make_query(),
|
||||
)
|
||||
|
||||
assert result == {'ok': True, 'path': '/workspace/root-owned.txt'}
|
||||
box_service.execute_tool.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_binary_file_as_base64_chunk():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
@@ -326,6 +352,35 @@ async def test_edit_replaces_unique_string():
|
||||
assert f.read() == 'def foo():\n return 42\n'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_falls_back_to_box_for_container_owned_workspace_file():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
path = os.path.join(tmpdir, 'root-owned.txt')
|
||||
with open(path, 'w') as f:
|
||||
f.write('before')
|
||||
box_service = SimpleNamespace(
|
||||
available=True,
|
||||
default_workspace=tmpdir,
|
||||
execute_tool=AsyncMock(
|
||||
return_value={
|
||||
'ok': True,
|
||||
'stdout': '{"ok": true, "path": "/workspace/root-owned.txt"}',
|
||||
}
|
||||
),
|
||||
)
|
||||
loader = NativeToolLoader(SimpleNamespace(box_service=box_service, logger=Mock()))
|
||||
|
||||
with patch('builtins.open', side_effect=PermissionError):
|
||||
result = await loader.invoke_tool(
|
||||
'edit',
|
||||
{'path': '/workspace/root-owned.txt', 'old_string': 'before', 'new_string': 'after'},
|
||||
_make_query(),
|
||||
)
|
||||
|
||||
assert result == {'ok': True, 'path': '/workspace/root-owned.txt'}
|
||||
box_service.execute_tool.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_rejects_ambiguous_match():
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
|
||||
Reference in New Issue
Block a user