fix: ACP set_model no longer runs queued prompts inside the RPC (review follow-up)

set_session_model's `finally` awaited _drain_queued_prompts, so a prompt queued
during a failed (or slow) switch_model ran a whole agent turn inside the
session/set_model request; the client received the RequestError only after
that turn ended, and the drained turn streamed with no open prompt request.
Schedule the drain via _schedule_soon so it runs right after the response is
queued, on both the success and the error path.
This commit is contained in:
teknium1
2026-09-20 01:10:10 -07:00
committed by Teknium
parent 0b98de9af2
commit a23984887a
2 changed files with 43 additions and 1 deletions

View File

@@ -1041,7 +1041,10 @@ class HermesACPAgent(SlashCommandsMixin, acp.Agent):
finally:
with state.runtime_lock:
state.command_op = False
await self._drain_queued_prompts(state, session_id, self._conn)
# Drain AFTER this response is queued, never inside it: a prompt that arrived
# mid-switch would otherwise run a whole turn before the client sees the
# (possibly failed) switch result.
self._schedule_soon(lambda: self._drain_queued_prompts(state, session_id, self._conn))
logger.info(
"Session %s: model switched to %s via provider %s", session_id, resolved_model, requested_provider
)

View File

@@ -179,3 +179,42 @@ def test_acp_set_session_model_rejection_is_invalid_params_and_leaves_session_un
asyncio.run(agent.set_session_model("other", "s1"))
assert not isinstance(rebuild_exc.value, RequestError)
assert state.model == "claude-sonnet-5" and state.agent is old_agent
def test_acp_set_session_model_does_not_run_queued_prompts_inside_the_rpc(monkeypatch):
"""A prompt that arrives while ``switch_model`` is off-loop is queued behind ``command_op``;
it must run AFTER the set_model response (error or success) is queued, never inside the RPC —
otherwise a failed switch is reported only after a whole agent turn."""
import asyncio
from acp.exceptions import RequestError
monkeypatch.setattr("hermes_cli.model_switch.switch_model",
lambda **_kw: ModelSwitchResult(success=False, error_message="`nope` is not a model"))
agent, _made = _acp_agent()
state = _state()
state.queued_prompts = ["hello, queued mid-switch"]
agent.session_manager.get_session = lambda sid: state
class _Conn:
async def session_update(self, *_a, **_k):
pass
agent._conn = _Conn()
ran: list = []
async def _prompt(*, prompt, session_id):
ran.append(prompt[0].text)
agent.prompt = _prompt
async def _run():
with pytest.raises(RequestError):
await agent.set_session_model("nope", "s1")
assert ran == [], "the queued prompt ran inside the set_model RPC"
await asyncio.sleep(0) # let the scheduled drain run once the response is out
await asyncio.sleep(0)
return list(ran)
assert asyncio.run(_run()) == ["hello, queued mid-switch"]
assert state.command_op is False