mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-11 16:26:02 +00:00
fix(agent-runner): align plugin runner runtime boundaries
This commit is contained in:
@@ -1256,7 +1256,13 @@ class TestValidateRunAuthorizationHelper:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_validate_test_helper', 'model', 'model_001', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_validate_test_helper',
|
||||
'model',
|
||||
'model_001',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
# Should return session, no error
|
||||
assert session is not None
|
||||
@@ -1310,6 +1316,7 @@ class TestValidateRunAuthorizationHelper:
|
||||
'model',
|
||||
'model_999', # Not in resources
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
# Should return no session, error response
|
||||
@@ -1342,7 +1349,13 @@ class TestValidateRunAuthorizationHelper:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_tool_test_helper', 'tool', 'web_search', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_tool_test_helper',
|
||||
'tool',
|
||||
'web_search',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is not None
|
||||
assert error is None
|
||||
@@ -1371,7 +1384,13 @@ class TestValidateRunAuthorizationHelper:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_kb_test_helper', 'knowledge_base', 'kb_001', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_kb_test_helper',
|
||||
'knowledge_base',
|
||||
'kb_001',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is not None
|
||||
assert error is None
|
||||
@@ -1548,7 +1567,13 @@ class TestRealActionHandlerSimulation:
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
# Step 1: Validate authorization
|
||||
session, error = await _validate_run_authorization('run_invoke_llm_flow_sim', 'model', 'model_001', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_invoke_llm_flow_sim',
|
||||
'model',
|
||||
'model_001',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
# Should pass authorization
|
||||
assert session is not None
|
||||
@@ -1582,7 +1607,13 @@ class TestRealActionHandlerSimulation:
|
||||
mock_ap.logger.warning = MagicMock()
|
||||
|
||||
# Try to access unauthorized model
|
||||
session, error = await _validate_run_authorization('run_reject_model_sim', 'model', 'model_999', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_reject_model_sim',
|
||||
'model',
|
||||
'model_999',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
# Should reject
|
||||
assert session is None
|
||||
@@ -1641,7 +1672,13 @@ class TestStoragePermissionValidation:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_plugin_storage_auth', 'storage', 'plugin', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_plugin_storage_auth',
|
||||
'storage',
|
||||
'plugin',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is not None
|
||||
assert error is None
|
||||
@@ -1670,7 +1707,13 @@ class TestStoragePermissionValidation:
|
||||
mock_ap.logger = MagicMock()
|
||||
mock_ap.logger.warning = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_plugin_storage_denied', 'storage', 'plugin', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_plugin_storage_denied',
|
||||
'storage',
|
||||
'plugin',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is None
|
||||
assert error is not None
|
||||
@@ -1700,7 +1743,11 @@ class TestStoragePermissionValidation:
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_workspace_storage_auth', 'storage', 'workspace', mock_ap
|
||||
'run_workspace_storage_auth',
|
||||
'storage',
|
||||
'workspace',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is not None
|
||||
@@ -1731,7 +1778,11 @@ class TestStoragePermissionValidation:
|
||||
mock_ap.logger.warning = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_workspace_storage_denied', 'storage', 'workspace', mock_ap
|
||||
'run_workspace_storage_denied',
|
||||
'storage',
|
||||
'workspace',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is None
|
||||
@@ -1769,7 +1820,13 @@ class TestFilePermissionValidation:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
session, error = await _validate_run_authorization('run_file_auth', 'file', 'file_001', mock_ap)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_file_auth',
|
||||
'file',
|
||||
'file_001',
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is not None
|
||||
assert error is None
|
||||
@@ -1803,6 +1860,7 @@ class TestFilePermissionValidation:
|
||||
'file',
|
||||
'file_999', # Not in resources
|
||||
mock_ap,
|
||||
caller_plugin_identity='test/runner',
|
||||
)
|
||||
|
||||
assert session is None
|
||||
@@ -1891,9 +1949,8 @@ class TestCallerPluginIdentityValidation:
|
||||
await registry.unregister('run_identity_mismatch')
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_caller_identity_allowed(self):
|
||||
"""_validate_run_authorization allows when caller_plugin_identity not provided."""
|
||||
# Unscoped plugin path: if caller_plugin_identity is None, skip identity check
|
||||
async def test_run_id_requires_caller_identity(self):
|
||||
"""Run-scoped authorization requires caller_plugin_identity."""
|
||||
from langbot.pkg.agent.runner.session_registry import get_session_registry
|
||||
|
||||
registry = get_session_registry()
|
||||
@@ -1912,18 +1969,17 @@ class TestCallerPluginIdentityValidation:
|
||||
mock_ap = MagicMock()
|
||||
mock_ap.logger = MagicMock()
|
||||
|
||||
# caller_plugin_identity not provided (None)
|
||||
session, error = await _validate_run_authorization(
|
||||
'run_no_caller_identity',
|
||||
'model',
|
||||
'model_001',
|
||||
mock_ap,
|
||||
caller_plugin_identity=None, # Not provided
|
||||
caller_plugin_identity=None,
|
||||
)
|
||||
|
||||
# Should pass (backward compat)
|
||||
assert session is not None
|
||||
assert error is None
|
||||
assert session is None
|
||||
assert error is not None
|
||||
assert 'caller_plugin_identity is required' in error.message
|
||||
|
||||
await registry.unregister('run_no_caller_identity')
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ def test_classify_python_workspace_detects_package_and_requirements():
|
||||
def test_wrap_python_command_with_env_contains_bootstrap_and_command():
|
||||
command = wrap_python_command_with_env('python script.py')
|
||||
|
||||
assert 'python -m venv "$_LB_VENV_DIR"' in command
|
||||
assert '_LB_SYSTEM_PYTHON="$(command -v python3 || command -v python || true)"' in command
|
||||
assert '"$_LB_SYSTEM_PYTHON" -m venv "$_LB_VENV_DIR"' in command
|
||||
assert 'export VIRTUAL_ENV="$_LB_VENV_DIR"' in command
|
||||
assert command.rstrip().endswith('python script.py')
|
||||
|
||||
|
||||
@@ -494,6 +494,47 @@ class TestBuildBoxProcessPayload:
|
||||
assert payload['args'] == ['/opt/other/server.py', '--flag']
|
||||
|
||||
|
||||
# ── Python Workspace Preparation ────────────────────────────────────
|
||||
|
||||
|
||||
class TestPythonWorkspacePreparation:
|
||||
def test_requirements_workspace_uses_venv_bootstrap(self, mcp_module, tmp_path):
|
||||
host_path = tmp_path / 'mcp-source'
|
||||
host_path.mkdir()
|
||||
(host_path / 'requirements.txt').write_text('mcp==1.26.0\n', encoding='utf-8')
|
||||
|
||||
command = mcp_module.BoxStdioSessionRuntime.detect_install_command(
|
||||
str(host_path),
|
||||
'/workspace/.mcp/u1/workspace',
|
||||
)
|
||||
|
||||
assert command is not None
|
||||
assert '_LB_SYSTEM_PYTHON="$(command -v python3 || command -v python || true)"' in command
|
||||
assert '"$_LB_SYSTEM_PYTHON" -m venv "$_LB_VENV_DIR"' in command
|
||||
assert 'python -m pip install -r "/workspace/.mcp/u1/workspace/requirements.txt"' in command
|
||||
assert 'pip install --no-cache-dir -r' not in command
|
||||
|
||||
def test_process_payload_can_start_from_prepared_python_env(self, mcp_module):
|
||||
payload = {
|
||||
'command': 'python',
|
||||
'args': ['/workspace/.mcp/u1/workspace/server.py'],
|
||||
'env': {},
|
||||
'cwd': '/workspace/.mcp/u1/workspace',
|
||||
}
|
||||
|
||||
wrapped = mcp_module.BoxStdioSessionRuntime._wrap_process_payload_with_python_env(
|
||||
payload,
|
||||
'/workspace/.mcp/u1/workspace',
|
||||
)
|
||||
|
||||
assert wrapped['command'] == 'sh'
|
||||
assert wrapped['args'][0] == '-lc'
|
||||
assert 'export VIRTUAL_ENV=/workspace/.mcp/u1/workspace/.venv' in wrapped['args'][1]
|
||||
assert 'export PATH=/workspace/.mcp/u1/workspace/.venv/bin:$PATH' in wrapped['args'][1]
|
||||
assert 'exec python /workspace/.mcp/u1/workspace/server.py' in wrapped['args'][1]
|
||||
assert wrapped['cwd'] == '/workspace/.mcp/u1/workspace'
|
||||
|
||||
|
||||
# ── get_runtime_info_dict ───────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -212,7 +212,8 @@ class TestSkillPathHelpers:
|
||||
|
||||
command = wrap_skill_command_with_python_env('python scripts/run.py')
|
||||
|
||||
assert 'python -m venv "$_LB_VENV_DIR"' in command
|
||||
assert '_LB_SYSTEM_PYTHON="$(command -v python3 || command -v python || true)"' in command
|
||||
assert '"$_LB_SYSTEM_PYTHON" -m venv "$_LB_VENV_DIR"' in command
|
||||
assert 'export VIRTUAL_ENV="$_LB_VENV_DIR"' in command
|
||||
assert command.rstrip().endswith('python scripts/run.py')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user