diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f996be..9e1b040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ 语义化版本见 `VERSION`(当前 0.1.0)。以下按日期倒序,只列已交付 的大项,每项附实测证据;未建成的东西在“诚实边界”里明说,不夸大。 +## 2026-10-28:网页搜索 Docker env 透传(e2e 失败后的修复) + +- 根因:Docker 任务容器不继承 worker 进程环境,只拿 + extra_env(-e);`SEARXNG_URL` 只写进 worker unit 的 + EnvironmentFile 时,容器内 codex -> MCP 根本看不到它, + auto 链跳过 SearXNG、只剩 DuckDuckGo,实弹 e2e 报错 + “all search providers failed: duckduckgo (DNS)”。 +- 修复:`cloud/worker.py _docker_task_context` 把 worker 环境 + 里已设置的 `SEARXNG_URL` / `PERPLEXITY_API_KEY` / + `EXA_API_KEY` / `TAVILY_API_KEY` / `BRAVE_API_KEY` 合并进 + extra_env(值不进日志;付费 key 与网关 key 同款走法)。 +- 单测:新增 2 个(有/无搜索 env 时的容器 env),连同 + websearch 旧测、租户隔离旧测共 25 个通过;cloud 全量 + 1038 个通过(skip 4)。只本地提交,未推、未建 PR、 + 未合并、未部署;重跑 e2e 须等本修复部署后。 + ## 2026-10-28:网页搜索接 worker + egress(公开 SearXNG 路) - `cloud/worker.py --websearch-mcp`(默认关):开启后 diff --git a/cloud/tests/test_websearch_worker.py b/cloud/tests/test_websearch_worker.py index a62c85e..95938db 100644 --- a/cloud/tests/test_websearch_worker.py +++ b/cloud/tests/test_websearch_worker.py @@ -99,6 +99,42 @@ class WorkerWebsearchTestCase(unittest.TestCase): self.assertEqual(config.count("[mcp_servers.websearch]"), 1) +class DockerEnvTestCase(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.db_path = os.path.join(self.tmp.name, "control.db") + init_db(self.db_path) + + def tearDown(self): + self.tmp.cleanup() + + def _docker_context(self): + worker = Worker(self.db_path, "/fake/host.py", + env={"XCM_API_KEY": "test-placeholder"}, + tenant_isolation=False, egress_proxy_port=None) + return worker._docker_task_context({"project_id": "projA"}) + + def test_searxng_url_passes_into_container_env(self): + from unittest import mock + with mock.patch.dict(os.environ, + {"SEARXNG_URL": "http://searx.diante.me"}): + ok, _, _, env, err = self._docker_context() + self.assertTrue(ok) + self.assertIsNone(err) + self.assertEqual(env.get("SEARXNG_URL"), "http://searx.diante.me") + + def test_no_search_env_means_no_extra_env(self): + from unittest import mock + with mock.patch.dict(os.environ, {}, clear=False): + for k in ("SEARXNG_URL", "PERPLEXITY_API_KEY", "EXA_API_KEY", + "TAVILY_API_KEY", "BRAVE_API_KEY"): + os.environ.pop(k, None) + ok, _, _, env, err = self._docker_context() + self.assertTrue(ok) + self.assertIsNone(err) + self.assertIsNone(env) + + class EgressFilterTestCase(unittest.TestCase): def test_search_domains_present_in_basic_form(self): lines = open(REPO_ROOT / "deploy" / "egress" / diff --git a/cloud/worker.py b/cloud/worker.py index 9046a54..cedb4c9 100644 --- a/cloud/worker.py +++ b/cloud/worker.py @@ -2200,6 +2200,20 @@ class Worker: "NO_PROXY": "localhost,127.0.0.1", "no_proxy": "localhost,127.0.0.1", } + # 搜索 env 透传:Docker 容器不继承 worker 进程环境,只拿 + # extra_env(-e),所以 worker 环境里的搜索配置若不显式 + # 放进这里,容器内 codex -> MCP 永远看不到 SEARXNG_URL, + # auto 链会跳过 SearXNG、只剩 DuckDuckGo(实弹 e2e 已证实)。 + # 只透传已设置的项,值绝不进日志;付费 key 与网关 key 一样 + # 走 extra_env(容器内进程本就需要它调 API)。 + search_env = { + k: os.environ[k] + for k in ("SEARXNG_URL", "PERPLEXITY_API_KEY", "EXA_API_KEY", + "TAVILY_API_KEY", "BRAVE_API_KEY") + if os.environ.get(k) + } + if search_env: + proxy_env = _merge_env(proxy_env, search_env) return (True, tenant_mounts, tenant_network, proxy_env, None) def _run_fork(self, task, quota_check=None):