fix(grok): surface a dead opencode-serve clearly instead of a zombie chat (M2)

If `opencode serve` died after the session opened, every subsequent turn failed
with an opaque httpx connection error while the container lingered. send() now
detects the exited subprocess (returncode set) and yields a clear error chunk +
turn_end so the panel shows a real "session ended — start a new chat" message;
the idle watchdog / a human reap then tears the container down.
This commit is contained in:
Renn F
2026-06-18 20:16:11 +02:00
parent 2f4c606f7f
commit a0b73cf69b
2 changed files with 33 additions and 0 deletions
@@ -9,7 +9,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from roboco.agent_sdk.opencode_session import (
OpencodeServeSession,
_extract_session_id,
_message_error,
normalize_opencode_message,
@@ -51,6 +53,21 @@ def test_fenced_draft_in_text_becomes_draft_chunk() -> None:
assert draft.data["title"] == "Add login"
@pytest.mark.asyncio
async def test_send_on_dead_serve_yields_clear_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
# A crashed `opencode serve` must surface a clear error + end the turn, not
# hang the chat with opaque connection errors while the container zombies.
sess = OpencodeServeSession()
monkeypatch.setattr(sess, "_session_id", "ses-1")
monkeypatch.setattr(sess, "_client", object()) # unused: dead-proc guard wins
monkeypatch.setattr(sess, "_proc", type("P", (), {"returncode": 1})())
chunks = [c async for c in sess.send("hi")]
assert [c.kind for c in chunks] == ["error", "turn_end"]
assert "exited" in chunks[0].text
def test_propose_draft_tool_part_becomes_draft_chunk() -> None:
# The intake-tools.js propose_draft tool call (its input nested under
# `draft`) is intercepted into a draft chunk — NOT rendered as a tool_use —