mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(grok): close the panel relay when the cost-cap kills an interactive chat (M4)
_enforce_grok_cost_budget killed + evicted a container directly. For the interactive roles (intake/secretary) that left the panel SSE relay open with no close sentinel, so the chat froze with no explanation. Add PrompterLiveRegistry.close_by_agent (push a final error event, then close every session bound to that agent) and call it from the cost-cap watchdog when the killed agent is the intake or secretary, so the panel reports the chat ended on the cost cap instead of hanging.
This commit is contained in:
@@ -3963,6 +3963,15 @@ class AgentOrchestrator:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
self._instances.pop(agent_id, None)
|
self._instances.pop(agent_id, None)
|
||||||
|
# Interactive roles (intake/secretary) have an open panel relay; a
|
||||||
|
# raw kill would leave the SSE hanging (frozen chat). Close it with a
|
||||||
|
# reason so the panel reports why the chat ended.
|
||||||
|
if agent_id in (INTAKE_AGENT_ID, SECRETARY_AGENT_ID):
|
||||||
|
from roboco.services.prompter_live import get_live_registry
|
||||||
|
|
||||||
|
get_live_registry().close_by_agent(
|
||||||
|
agent_id, error="Chat ended: the Grok cost cap was exceeded."
|
||||||
|
)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"grok container killed: cost ceiling exceeded",
|
"grok container killed: cost ceiling exceeded",
|
||||||
agent_id=agent_id,
|
agent_id=agent_id,
|
||||||
|
|||||||
@@ -101,6 +101,25 @@ class PrompterLiveRegistry:
|
|||||||
session.queue.put_nowait(_CLOSE)
|
session.queue.put_nowait(_CLOSE)
|
||||||
self.log.info("Live intake session closed", session_id=session_id)
|
self.log.info("Live intake session closed", session_id=session_id)
|
||||||
|
|
||||||
|
def close_by_agent(self, agent_id: str, *, error: str | None = None) -> list[str]:
|
||||||
|
"""Close every live session bound to ``agent_id`` (e.g. on a forced kill).
|
||||||
|
|
||||||
|
When the orchestrator kills an interactive container out-of-band (the
|
||||||
|
Grok cost-cap watchdog), the relay would otherwise stay open and the
|
||||||
|
panel SSE would hang forever. This pushes an optional final ``error``
|
||||||
|
event so the panel shows WHY the chat ended, then closes the stream.
|
||||||
|
Returns the closed session ids.
|
||||||
|
"""
|
||||||
|
closed: list[str] = []
|
||||||
|
for session_id, session in list(self._sessions.items()):
|
||||||
|
if session.agent_id != agent_id or session.closed:
|
||||||
|
continue
|
||||||
|
if error:
|
||||||
|
session.queue.put_nowait({"kind": "error", "text": error})
|
||||||
|
self.close(session_id)
|
||||||
|
closed.append(session_id)
|
||||||
|
return closed
|
||||||
|
|
||||||
def park(self, session_id: str, task_id: str) -> bool:
|
def park(self, session_id: str, task_id: str) -> bool:
|
||||||
"""Mark a session as parked awaiting board review of ``task_id``.
|
"""Mark a session as parked awaiting board review of ``task_id``.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ def test_open_get_close() -> None:
|
|||||||
assert reg.get("s1") is None
|
assert reg.get("s1") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_close_by_agent_closes_matching_sessions_with_error() -> None:
|
||||||
|
reg = PrompterLiveRegistry()
|
||||||
|
reg.open("s1", "intake-1")
|
||||||
|
reg.open("s2", "secretary-1") # different agent — must survive
|
||||||
|
closed = reg.close_by_agent("intake-1", error="cost cap")
|
||||||
|
assert closed == ["s1"]
|
||||||
|
assert reg.get("s1") is None # closed + popped
|
||||||
|
assert reg.is_alive("s2") # untouched
|
||||||
|
# The error event is queued before the close sentinel so the panel sees it.
|
||||||
|
sess = reg.open("s3", "intake-1")
|
||||||
|
reg.close_by_agent("intake-1", error="boom")
|
||||||
|
assert sess.queue.get_nowait() == {"kind": "error", "text": "boom"}
|
||||||
|
|
||||||
|
|
||||||
def test_park_and_find_by_task() -> None:
|
def test_park_and_find_by_task() -> None:
|
||||||
"""A parked session is discoverable by task id for board-feedback injection."""
|
"""A parked session is discoverable by task id for board-feedback injection."""
|
||||||
reg = PrompterLiveRegistry()
|
reg = PrompterLiveRegistry()
|
||||||
|
|||||||
Reference in New Issue
Block a user