From a777437594732539cae6975681d8f0b21c74d60b Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:47:29 +0800 Subject: [PATCH] Keep oversized diagnostics out of private chat backlog reads Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../native_chat/external_conversations.py | 16 ++++++++++------ tests/test_lark_private_conversations.py | 8 ++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/loopx/capabilities/native_chat/external_conversations.py b/loopx/capabilities/native_chat/external_conversations.py index 1903219e23..8e08e0f06e 100644 --- a/loopx/capabilities/native_chat/external_conversations.py +++ b/loopx/capabilities/native_chat/external_conversations.py @@ -353,14 +353,18 @@ def flush() -> None: def _quota_pending_inputs(self, row: dict[str, Any]) -> dict[str, Any]: store = self.controller.store turn = store.load_turn(row["session_id"], row["turn_id"]) + session = store.load_session(row["session_id"]) + # Keep the full request/response evidence. The related Session and + # Turn need only identity, lifecycle and result for this read. return { "request": {key: value for key, value in row.items() if key != "attachments"}, - "current_session": store.load_session(row["session_id"]), - # Pending-work semantics use status/response, not image bytes. - # Accepted Chat images can exceed the ordinary Core message limit; - # retain them in the canonical Turn, outside this read-only view. - "pending_observation": {"turn": ({key: value for key, value in turn.items() if key != "attachments"} - if turn is not None else None), "stop_observed": False}, + "current_session": ({key: session[key] for key in ("session_id", "status") if key in session} + if session is not None else None), + "pending_observation": { + "turn": ({key: turn[key] for key in ("session_id", "turn_id", "client_turn_id", "status", + "error_code", "response") if key in turn} if turn is not None else None), + "stop_observed": False, + }, } def quota_backlog_pending(self, row: dict[str, Any], *, diff --git a/tests/test_lark_private_conversations.py b/tests/test_lark_private_conversations.py index 65bd275297..bbee4ebcc7 100644 --- a/tests/test_lark_private_conversations.py +++ b/tests/test_lark_private_conversations.py @@ -2242,6 +2242,10 @@ def quota_exhausted(*_args, **_kwargs): # does not retire the retained original work or authorize a replay. transport.reconcile() assert json.loads((transport.root / f"{row['request_ref']}.json").read_text())["status"] == "delivered" + # An old Turn can retain a large diagnostic without changing its + # structured failure. The read-only health route must still show the + # pending original, even when that diagnostic cannot fit a Core RPC. + store.update_turn(row["session_id"], row["turn_id"], error="synthetic diagnostic " * 450_000) def durable_state(): return {path: path.read_bytes() for path in store.root.rglob("*") @@ -2249,8 +2253,6 @@ def durable_state(): before = durable_state() writes = list(provider.writes) - assert transport.core.quota_backlog_pending(row) - assert transport.core.quota_backlog_observer()([row]) == [True] connection = http.client.HTTPConnection(*server.server_address, timeout=15) try: connection.request("GET", "/api/chat/lark/private-conversations") @@ -2261,6 +2263,8 @@ def durable_state(): connection.close() binding = next(item for item in result["connections"] if item["binding_id"] == row["binding_id"]) assert binding["pending_count"] == 1 and binding["recovery_count"] == 0 + assert transport.core.quota_backlog_pending(row) + assert transport.core.quota_backlog_observer()([row]) == [True] assert durable_state() == before and provider.writes == writes assert not ordinary[4].exists() or '"method": "turn/start"' not in ordinary[4].read_text() store.update_session(row["session_id"], status="closed")