feat: delegate sandbox policy to runners and simplify pipeline migration

This commit is contained in:
RockChinQ
2026-09-17 23:05:27 +08:00
parent 8c119bc4b6
commit b264d46d77
57 changed files with 2111 additions and 1751 deletions
@@ -2,7 +2,6 @@
from __future__ import annotations
import json
from langbot_plugin.api.entities.builtin.runner.delivery import DeliveryContext
from langbot_plugin.api.entities.builtin.runner.input import AgentInput
@@ -12,13 +11,10 @@ from langbot_plugin.api.entities.builtin.provider.message import ContentElement
from langbot.pkg.agent.runner.execution_context import (
append_mcp_resource_context_to_event,
build_execution_query,
build_host_box_scope,
prepare_box_scope,
prepare_execution_query,
project_mcp_resource_config,
)
from langbot.pkg.agent.runner.host_models import AgentEventEnvelope
from langbot.pkg.utils import constants
class PlatformAdapter:
@@ -54,79 +50,14 @@ def make_event(
)
def test_pipeline_and_event_execution_use_same_platform_session_scope(monkeypatch):
monkeypatch.setattr(constants, 'instance_id', 'instance-1')
def test_query_preparation_does_not_choose_a_box():
event = make_event()
query = pipeline_query.Query.model_construct(
query_id=1,
launcher_type='person',
launcher_id='user-1',
sender_id='user-1',
adapter=PlatformAdapter(),
variables={},
)
query = pipeline_query.Query.model_construct(variables={})
prepare_execution_query(query, event, ['pdf'])
event_query = build_execution_query(event, ['pdf'])
assert query.variables['_host_box_scope'] == event_query.variables['_host_box_scope']
assert query.variables['_pipeline_bound_skills'] == ['pdf']
assert event_query.variables['_pipeline_bound_skills'] == ['pdf']
scope = json.loads(query.variables['_host_box_scope'])
assert scope == {
'instance_id': 'instance-1',
'workspace_id': 'workspace-1',
'bot_id': 'bot-1',
'platform_adapter': 'PlatformAdapter',
'target_type': 'person',
'target_id': 'user-1',
'thread_id': None,
}
def test_prepare_box_scope_does_not_change_existing_skill_projection():
event = make_event()
query = pipeline_query.Query.model_construct(
query_id=1,
launcher_type='person',
launcher_id='user-1',
variables={'_pipeline_bound_skills': ['existing']},
)
variables = prepare_box_scope(query, event)
assert variables['_host_box_scope']
assert variables['_pipeline_bound_skills'] == ['existing']
def test_prepare_box_scope_preserves_event_first_channel_scope():
channel_event = make_event(target_type='channel', target_id='same')
channel_query = build_execution_query(channel_event, [])
original_scope = channel_query.variables['_host_box_scope']
prepare_execution_query(channel_query, channel_event, [])
person_scope = build_execution_query(make_event(target_type='person', target_id='same'), []).variables[
'_host_box_scope'
]
assert channel_query.variables['_host_box_scope'] == original_scope
assert json.loads(original_scope)['target_type'] == 'channel'
assert original_scope != person_scope
def test_prepare_box_scope_overwrites_untrusted_existing_scope():
event = make_event(target_type='person', target_id='user-1')
query = pipeline_query.Query.model_construct(
query_id=1,
launcher_type='person',
launcher_id='user-1',
variables={'_host_box_scope': 'forged-scope'},
)
variables = prepare_box_scope(query, event)
assert variables['_host_box_scope'] != 'forged-scope'
assert json.loads(variables['_host_box_scope'])['target_id'] == 'user-1'
assert query.variables == {'_pipeline_bound_skills': ['pdf']}
assert event_query.variables == {'_pipeline_bound_skills': ['pdf']}
assert getattr(query, '_box_binding', None) is None
def test_project_mcp_resource_config_uses_independent_runner_settings():
@@ -187,10 +118,7 @@ def test_event_reply_target_populates_valid_session_identity():
assert query.sender_id == 'room-1'
assert query.session.launcher_type.value == 'group'
assert query.session.launcher_id == 'room-1'
scope = json.loads(query.variables['_host_box_scope'])
assert scope['target_type'] == 'group'
assert scope['target_id'] == 'room-1'
assert 'rotating-transcript-id' not in query.variables['_host_box_scope']
assert '_host_box_scope' not in query.variables
def test_non_message_event_without_conversation_uses_event_scope():
@@ -205,21 +133,6 @@ def test_non_message_event_without_conversation_uses_event_scope():
query = build_execution_query(event, [])
scope = json.loads(query.variables['_host_box_scope'])
assert scope['target_type'] == 'event'
assert scope['target_id'] == event.event_id
assert '_host_box_scope' not in query.variables
assert query.pipeline_config is None
assert query.pipeline_uuid is None
def test_scope_isolated_by_instance_and_platform_adapter(monkeypatch):
event = make_event(adapter='AdapterA')
monkeypatch.setattr(constants, 'instance_id', 'instance-a')
first = build_host_box_scope(event)
monkeypatch.setattr(constants, 'instance_id', 'instance-b')
second = build_host_box_scope(event)
other_adapter = build_host_box_scope(make_event(adapter='AdapterB'))
assert first != second
assert second != other_adapter