"""声明式 agent 定义 v2 单测:注册表、落库、developerInstructions 通道。 运行(在仓库根目录下): python3 -m unittest discover -s cloud/tests -p "test_agent_def_v2.py" -v 覆盖:agent_name 查注册表、agent 与 agent_name 同给 400、 未知 agent_name 400、本地/docker 派发命令含 --developer-instructions、无指令时命令不变、host dispatch 把 指令传给 create_thread 的 instructions(即 developerInstructions)。 不跑真实模型、不联网;host 部分用假 server,只断言传参。 """ import http.client import importlib.util import json import os import sys import tempfile import threading import unittest from pathlib import Path try: from cloud.agent_def import default_agents_dir from cloud.auth import AuthStore from cloud.sandbox import DockerExecutor, LocalExecutor from cloud.server import create_server except ImportError: sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.agent_def import default_agents_dir from cloud.auth import AuthStore from cloud.sandbox import DockerExecutor, LocalExecutor from cloud.server import create_server def _load_host(): spec = importlib.util.spec_from_file_location( "hostmod_v2", str(Path(__file__).resolve().parents[2] / "host.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class ServerAgentNameTestCase(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.db_path = os.path.join(self.tmp.name, "control.db") self.auth = AuthStore(self.db_path) self.owner_id, self.owner_key = self.auth.create_user("owner") self.server = create_server(self.db_path, "127.0.0.1", 0) self.port = self.server.server_address[1] self.thread = threading.Thread( target=self.server.serve_forever, daemon=True) self.thread.start() status, self.project = self._req( "POST", "/projects", {"name": "demo"}, self.owner_key) self.assertEqual(status, 201) def tearDown(self): self.server.shutdown() self.server.server_close() self.thread.join(timeout=5) try: self.auth.conn.close() except Exception: pass self.tmp.cleanup() def _req(self, method, path, body=None, key=None): conn = http.client.HTTPConnection("127.0.0.1", self.port, timeout=10) headers = {"Content-Type": "application/json"} if key: headers["X-API-Key"] = key conn.request(method, path, json.dumps(body) if body is not None else None, headers) resp = conn.getresponse() raw = resp.read() conn.close() try: data = json.loads(raw) except ValueError: data = None return resp.status, data def _list_tasks(self): status, data = self._req( "GET", "/tasks?project_id=" + self.project["id"], key=self.owner_key) self.assertEqual(status, 200) return data["tasks"] def test_agent_name_from_registry(self): # 注册表里有 agents/research-brief.json(仓库自带示例)。 self.assertTrue((default_agents_dir() / "research-brief.json").is_file()) status, task = self._req("POST", "/tasks", { "project_id": self.project["id"], "prompt": "do research", "agent_name": "research-brief"}, self.owner_key) self.assertEqual(status, 201, task) self.assertEqual(task["agent_name"], "research-brief") self.assertEqual(task["agent_version"], "v1") self.assertEqual(task["effort"], "medium") self.assertEqual(task["executor"], "docker") self.assertEqual(task["prompt"], "do research") stored = self._list_tasks()[0] self.assertEqual(stored["agent_instructions"][:20], "Confirm the question") def test_agent_and_agent_name_conflict(self): status, _data = self._req("POST", "/tasks", { "project_id": self.project["id"], "prompt": "do research", "agent": {"name": "research-brief"}, "agent_name": "research-brief"}, self.owner_key) self.assertEqual(status, 400) self.assertEqual(self._list_tasks(), []) def test_unknown_agent_name(self): status, _data = self._req("POST", "/tasks", { "project_id": self.project["id"], "prompt": "do research", "agent_name": "no-such-agent"}, self.owner_key) self.assertEqual(status, 400) self.assertEqual(self._list_tasks(), []) class InstructionsChannelTestCase(unittest.TestCase): def test_local_dispatch_includes_instructions(self): captured = [] def runner(cmd, env=None): captured.append(cmd) return {"returncode": 0, "stdout": "ok", "stderr": ""} ex = LocalExecutor(host_py_path="host.py", runner=runner, repo="/tmp/repo") ex.dispatch_task({"id": "t1", "prompt": "hi", "agent_instructions": "be careful"}) self.assertIn("--developer-instructions", captured[0]) idx = captured[0].index("--developer-instructions") self.assertEqual(captured[0][idx + 1], "be careful") self.assertLess(idx, captured[0].index("dispatch")) def test_local_dispatch_without_instructions_unchanged(self): captured = [] def runner(cmd, env=None): captured.append(cmd) return {"returncode": 0, "stdout": "ok", "stderr": ""} ex = LocalExecutor(host_py_path="host.py", runner=runner, repo="/tmp/repo") ex.dispatch_task({"id": "t1", "prompt": "hi"}) self.assertNotIn("--developer-instructions", captured[0]) def test_docker_inner_command_includes_instructions(self): ex = DockerExecutor.__new__(DockerExecutor) ex.config_path = None ex.model = None ex.ignore_host_budget = False cmd = ex._inner_host_command( "dispatch", model="m", developer_instructions="be careful") self.assertIn("--developer-instructions", cmd) self.assertEqual(cmd[cmd.index("--developer-instructions") + 1], "be careful") def test_host_dispatch_passes_instructions(self): host = _load_host() class FakeServer: def __init__(self): self.calls = [] def create_thread(self, cwd, model=None, sandbox=None, approval_policy=None, instructions=None): self.calls.append(instructions) return "thr1" srv = FakeServer() state = {"tasks": {}} with tempfile.TemporaryDirectory() as tmpdir: host.dispatch(srv, state, "t1", "hi", tmpdir, "m", "low", None, None, use_worktree=False, developer_instructions="be careful") self.assertEqual(srv.calls, ["be careful"])