From 4a8c7ce6e70fd0748fc0c0e9560703b815899fbe Mon Sep 17 00:00:00 2001 From: Stefan van Biljon Date: Fri, 18 Sep 2026 23:32:19 -0700 Subject: [PATCH] fix(acp): forward the resolved credential pool into ACP agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve_runtime_provider() selects a provider-scoped credential pool and returns it as runtime["credential_pool"]; oneshot and the gateway hand it to AIAgent, but acp_adapter/session.py::_make_agent dropped it, so a long-lived ACP process had _credential_pool=None and could not refresh/rotate on HTTP 401 after OAuth token expiry — the only recovery was restarting the ACP process (#70292). Forward the pool by identity like the other surfaces. The pool is already provider-scoped and its selected entry matches the agent's initial api_key, so the existing account-isolation guards are preserved rather than bypassed. Salvaged from PR #70293 (the cherry-pick claimed in #77029 never reached acp_adapter/session.py); regression test asserts the pool object is retained. Fixes #70292 --- acp_adapter/session.py | 1 + tests/acp_adapter/test_session.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/acp_adapter/session.py b/acp_adapter/session.py index 8227a78354..5b870a5320 100644 --- a/acp_adapter/session.py +++ b/acp_adapter/session.py @@ -423,6 +423,7 @@ class SessionManager: kwargs.update({ "provider": runtime.get("provider"), "api_mode": api_mode or runtime.get("api_mode"), "base_url": base_url or runtime.get("base_url"), "api_key": runtime.get("api_key"), + "credential_pool": runtime.get("credential_pool"), "command": runtime.get("command"), "args": list(runtime.get("args") or []), }) except Exception: diff --git a/tests/acp_adapter/test_session.py b/tests/acp_adapter/test_session.py index 07ce76751d..786474f83e 100644 --- a/tests/acp_adapter/test_session.py +++ b/tests/acp_adapter/test_session.py @@ -142,6 +142,28 @@ class TestCreateSession: assert (seen[0]["enabled_toolsets"], seen[0]["disabled_toolsets"]) == (["hermes-acp", "mcp-cfg-server"], None) assert (seen[1]["enabled_toolsets"], seen[1]["disabled_toolsets"]) == (["hermes-acp", "mcp-acp-server"], ["browser"]) + def test_make_agent_forwards_resolved_credential_pool(self, monkeypatch): + """#70292: the provider-scoped credential pool selected by resolve_runtime_provider reaches the + ACP agent by identity, so a long-lived session can refresh/rotate on 401 instead of needing a restart.""" + seen: list[dict] = [] + sentinel_pool = object() + + class FakeAgent: + def __init__(self, **kwargs): + seen.append(kwargs) + + monkeypatch.setattr("run_agent.AIAgent", FakeAgent) + monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"model": {"default": "m", "provider": "openai-codex"}}) + monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", lambda **_kw: { + "provider": "openai-codex", "api_mode": "codex_app_server", "api_key": "test-key", "credential_pool": sentinel_pool, + }) + monkeypatch.setattr("hermes_cli.mcp_startup.ensure_mcp_discovery_before_agent_build", lambda **_kw: None) + monkeypatch.setattr("acp_adapter.session._register_task_cwd", lambda task_id, cwd: None) + + SessionManager(db=None)._make_agent(session_id="s", cwd=".") + + assert seen[0]["credential_pool"] is sentinel_pool +