Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/agentpool_server/opencode_server/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ def create(cls, session: Session) -> Self:


class SessionDeletedProperties(SessionIdProperties):
"""Properties for session deleted event."""
"""Properties for session deleted event.

Carries the full session ``info`` so OpenCode TUI clients can read
``properties.info.id`` to optimistically hide deleted sessions, matching
the upstream OpenCode ``session.deleted`` event schema which includes both
``sessionID`` and ``info``.
"""

info: Session


class SessionDeletedEvent(OpenCodeBaseModel):
Expand All @@ -101,8 +109,8 @@ class SessionDeletedEvent(OpenCodeBaseModel):
properties: SessionDeletedProperties

@classmethod
def create(cls, session_id: str) -> Self:
return cls(properties=SessionDeletedProperties(session_id=session_id))
def create(cls, session: Session) -> Self:
return cls(properties=SessionDeletedProperties(session_id=session.id, info=session))


class SessionStatusProperties(SessionIdProperties):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ async def delete_session(session_id: str, state: StateDep) -> bool:
session_pool = state.pool.session_pool
if session_pool is not None and session_pool.sessions.store is not None:
await session_pool.sessions.store.delete_session(session_id)
await state.broadcast_event(SessionDeletedEvent.create(session_id))
await state.broadcast_event(SessionDeletedEvent.create(session))
return True


Expand Down
2 changes: 1 addition & 1 deletion tests/servers/opencode_server/test_global_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ def _build_handled_event(event_type: type) -> Event: # noqa: PLR0911
"""Build a handled event with session_id='abc' using the appropriate constructor."""
sid = "abc"
if event_type is SessionDeletedEvent:
return SessionDeletedEvent.create(session_id=sid)
return SessionDeletedEvent.create(session=_make_session(sid))
if event_type is SessionStatusEvent:
return SessionStatusEvent.create(session_id=sid, status_type="busy")
if event_type is SessionIdleEvent:
Expand Down
28 changes: 25 additions & 3 deletions tests/servers/opencode_server/test_sse_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ async def test_tui_filter_all_session_events_pass() -> None:
session_events: list[Event] = [
SessionStatusEvent.create(session_id="s1", status_type="busy"),
SessionCompactedEvent.create(session_id="s1"),
SessionDeletedEvent.create(session_id="s1"),
SessionDeletedEvent.create(session=_make_session("s1")),
TodoUpdatedEvent.create(session_id="s1", todos=[]),
PartDeltaEvent.create(session_id="s1", message_id="m1", part_id="p1", delta="x"),
]
Expand Down Expand Up @@ -473,6 +473,28 @@ async def test_session_id_in_payload_session_created() -> None:
assert data["sessionId"] == "top3"


@pytest.mark.anyio
async def test_session_deleted_event_carries_info() -> None:
"""SessionDeletedEvent carries full ``info`` for OpenCode TUI clients.

Clients read ``properties.info.id`` to hide deleted sessions.

Regression test: the event previously only carried ``sessionID`` with no
``info`` field, so the OpenCode TUI's ``event.properties.info.id`` access
threw and deleted sessions stayed visible in the session list.
"""
session = _make_session("del1")
event = SessionDeletedEvent.create(session=session)
# model-level: properties expose both sessionID and info.id
assert event.properties.session_id == "del1"
assert event.properties.info.id == "del1"
# wire-level: serialized payload keeps info.id reachable for the TUI
data = json.loads(_serialize_event(event, wrap_payload=False))
assert data["type"] == "session.deleted"
assert data["properties"]["sessionID"] == "del1"
assert data["properties"]["info"]["id"] == "del1"


@pytest.mark.anyio
async def test_session_id_in_payload_message_updated() -> None:
"""MessageUpdatedEvent payload has sessionId at top level."""
Expand Down Expand Up @@ -949,11 +971,11 @@ def test_session_id_consistency_command_executed() -> None:

# Re-use the same event factories from test_global_event.py
_ALL_HANDLED_EVENTS_WITH_SID: list[tuple[str, Event]] = [
("session.deleted", SessionDeletedEvent.create(session_id="ex1")),
("session.deleted", SessionDeletedEvent.create(session=_make_session("ex1"))),
("session.status", SessionStatusEvent.create(session_id="ex2", status_type="busy")),
("session.idle", SessionCompactedEvent.create(session_id="ex3")),
("session.compacted", SessionCompactedEvent.create(session_id="ex4")),
("message.removed", SessionDeletedEvent.create(session_id="ex5")),
("message.removed", SessionDeletedEvent.create(session=_make_session("ex5"))),
(
"message.part.delta",
PartDeltaEvent.create(session_id="ex6", message_id="m1", part_id="p1", delta="x"),
Expand Down
Loading