| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- import importlib.util
- import json
- import sys
- import tempfile
- import threading
- import time
- import unittest
- from pathlib import Path
- from types import SimpleNamespace
- from unittest.mock import patch
- spec = importlib.util.spec_from_file_location('test_yaoyao_bridge', Path(__file__).parents[1] / 'bridge_runtime.py')
- bridge = importlib.util.module_from_spec(spec)
- sys.modules[spec.name] = bridge
- spec.loader.exec_module(bridge)
- class ComputerPolicyTests(unittest.TestCase):
- def setUp(self):
- self.temp = tempfile.TemporaryDirectory()
- self.home = Path(self.temp.name)
- self.agent = SimpleNamespace(session_id='stored', enabled_toolsets=['all'])
- self.session = {'agent': self.agent, 'session_key': 'stored', 'running': True, 'cwd': str(self.home),
- '_yaoyao_computer_policy': {'mode': 'isolated', 'hostAccess': False}}
- self.server = SimpleNamespace(_sessions={'runtime': self.session}, _sessions_lock=threading.RLock())
- self.binding = bridge.Binding('runtime', 'stored', 'default', self.home, 'generation',
- 'http://127.0.0.1:12345', 'a' * 40, time.time() * 1000 + 60000,
- ('user', 'password'), self.session, self.agent,
- computer_policy=dict(self.session['_yaoyao_computer_policy']))
- bridge._bindings.clear(); bridge._generations.clear(); bridge._children.clear()
- bridge._bindings['runtime'] = self.binding
- self.patches = [patch.dict(sys.modules, {'agent.redact': SimpleNamespace(redact_sensitive_text=lambda text, **kw: text)}),
- patch.object(bridge, '_server', return_value=self.server),
- patch.object(bridge, '_check_session_mode'),
- patch.object(bridge, '_transport_identity', return_value=self.binding.owner),
- patch.object(bridge, '_profile_home', return_value=self.home)]
- for p in self.patches: p.start()
- def tearDown(self):
- for p in reversed(self.patches): p.stop()
- self.temp.cleanup()
- def test_isolated_skills_and_vm_tools_use_native_catalog_but_host_operations_require_approval(self):
- for name in ['skills_list', 'skill_view', 'vision_analyze', 'yaoyao_computer_shell_aabbcc']:
- self.assertIsNone(bridge.computer_directive(session_id='stored', tool_name=name, args={}))
- for name in ['terminal', 'process_manage', 'read_file', 'write_file', 'execute_code', 'delegate_task', 'session_search', 'browser_navigate', 'yaoyao_computer_copy_file_aabbcc']:
- result = bridge.computer_directive(session_id='stored', tool_name=name, args={'path': '/authorized.txt'})
- self.assertEqual(result['action'], 'approve')
- self.assertIn('/authorized.txt', result['message'])
- self.binding.computer_policy['hostAccess'] = True
- self.assertIsNone(bridge.computer_directive(session_id='stored', tool_name='terminal', args={}))
- self.binding.computer_policy.update(mode='profile', hostAccess=False)
- self.assertIsNone(bridge.computer_directive(session_id='stored', tool_name='terminal', args={}))
- def test_stopped_expired_unbound_and_replaced_sessions_cannot_call_native_tools(self):
- for change in ['stop', 'expire', 'unbind', 'replace']:
- with self.subTest(change=change):
- self.session.update(running=True, agent=self.agent)
- self.binding.expires_at = time.time() * 1000 + 60000
- bridge._bindings['runtime'] = self.binding
- if change == 'stop': self.session['running'] = False
- if change == 'expire': self.binding.expires_at = 0
- if change == 'unbind': bridge._bindings.clear()
- if change == 'replace': self.session['agent'] = SimpleNamespace(session_id='stored')
- self.assertEqual(bridge.computer_directive(session_id='runtime', tool_name='skill_view', args={})['action'], 'block')
- def test_ordinary_profile_sessions_are_unaffected(self):
- self.session.pop('_yaoyao_computer_policy')
- self.assertIsNone(bridge.computer_directive(session_id='stored', tool_name='terminal', args={}))
- def test_policy_cannot_be_changed_during_renewal(self):
- self.session['running'] = False
- body = {'session_id': 'runtime', 'stored_session_id': 'stored', 'profile': 'default',
- 'generation': 'generation', 'bridge_url': self.binding.bridge_url, 'token': self.binding.token,
- 'expires_at': time.time() * 1000 + 60000, 'computer_policy': {'mode': 'profile', 'hostAccess': True}}
- with patch.object(bridge, '_agent_has_tools', return_value=True):
- with self.assertRaises(bridge.BridgeError) as error:
- bridge.bind(self.binding.owner, body)
- self.assertEqual(error.exception.code, 'generation_conflict')
- def test_transfer_is_owned_by_hermes_and_checks_generation_owner_and_protected_paths(self):
- constants = SimpleNamespace(set_hermes_home_override=lambda p: None, reset_hermes_home_override=lambda p: None)
- file_tools = SimpleNamespace(_check_sensitive_path=lambda p, t: 'denied' if p.endswith('protected') else None,
- _check_protected_instruction_write=lambda p, t: None,
- _check_approval_required_write=lambda p, t: None)
- safety = SimpleNamespace(get_read_block_error=lambda p: 'denied' if p.endswith('protected') else None)
- modules = {'hermes_constants': constants, 'tools.file_tools': file_tools, 'agent.file_safety': safety}
- (self.home / 'authorized').write_bytes(b'authorized-input')
- body = {'session_id': 'runtime', 'generation': 'generation', 'action': 'read', 'path': 'authorized'}
- with patch.dict(sys.modules, modules):
- self.assertEqual(bridge.computer_file(self.binding.owner, body)['data'], 'YXV0aG9yaXplZC1pbnB1dA==')
- for owner, changed in [(('other', 'password'), body), (self.binding.owner, {**body, 'generation': 'stale'}),
- (self.binding.owner, {**body, 'path': 'protected'})]:
- with self.assertRaises(bridge.BridgeError): bridge.computer_file(owner, changed)
- bridge.computer_file(self.binding.owner, {**body, 'action': 'write', 'path': 'result', 'data': 'cmVzdWx0'})
- self.assertEqual((self.home / 'result').read_bytes(), b'result')
- self.session['running'] = False
- with self.assertRaises(bridge.BridgeError): bridge.computer_file(self.binding.owner, body)
- if __name__ == '__main__':
- unittest.main()
|