From 6be1601ccd01c040177fad33e603720b2f33c43a Mon Sep 17 00:00:00 2001 From: Pheby Date: Wed, 9 Sep 2026 06:54:56 +0000 Subject: [PATCH] Anchor generated attachments to assistant messages Deliverables now inherit the active assistant draft message ID so clients can render them beside the reply that produced them. --- docs/PROTOCOL.md | 2 +- plugin/pheby/adapter.py | 9 ++++++++- tests/test_pheby.py | 8 ++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 17ab02e..749455e 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -309,7 +309,7 @@ adapter-managed storage and broadcasts: "kind": "image" | "voice" | "video" | "audio" | "document", "inline_image": false, "conversation_id": "a1b2…", - "message_id": null, + "message_id": "draft-8c1f…" | null, "created_at": "2026-09-02T18:30:00+00:00", "expires_at": "2026-09-09T18:30:00+00:00", // null when retention=0 "download_path": "/attachments/e5f6…" }, diff --git a/plugin/pheby/adapter.py b/plugin/pheby/adapter.py index ffb9aed..b85707f 100644 --- a/plugin/pheby/adapter.py +++ b/plugin/pheby/adapter.py @@ -379,11 +379,18 @@ class PhebyAdapter(BasePlatformAdapter): return SendResult(success=True, message_id=clarify_id) # ── deliverables (attachments) ─────────────────────────────────────── + def _active_assistant_message_id(self, conversation_id: str) -> Optional[str]: + """Return the live assistant draft that a deliverable belongs beneath.""" + draft = self._drafts.get(conversation_id) + message_id = draft.get("message_id") if isinstance(draft, dict) else None + return str(message_id) if message_id else None + async def _register_and_broadcast( self, file_path: str, conversation_id: str, *, + message_id: Optional[str] = None, kind_hint: Optional[str] = None, filename: Optional[str] = None, ) -> Optional[Dict[str, Any]]: @@ -392,7 +399,7 @@ class PhebyAdapter(BasePlatformAdapter): desc = await self._server.store.register_file( file_path, conversation_id=conversation_id, - message_id=None, + message_id=message_id or self._active_assistant_message_id(conversation_id), filename=filename, kind_hint=kind_hint, ) diff --git a/tests/test_pheby.py b/tests/test_pheby.py index cd9c27f..13460a9 100644 --- a/tests/test_pheby.py +++ b/tests/test_pheby.py @@ -28,6 +28,7 @@ if str(_PLUGIN_DIR) not in sys.path: from pheby import protocol as proto # noqa: E402 from pheby.attachments import AttachmentStore, constant_time_equals # noqa: E402 +from pheby.adapter import PhebyAdapter # noqa: E402 from pheby.config import load_config # noqa: E402 from pheby.conversations import ConversationRouter # noqa: E402 from pheby.server import PhebyServer # noqa: E402 @@ -344,6 +345,13 @@ class TestConversations: # Attachments # ═══════════════════════════════════════════════════════════════════════════ class TestAttachments: + def test_adapter_anchors_deliverable_to_active_assistant_draft(self): + adapter = object.__new__(PhebyAdapter) + adapter._drafts = {"conv": {"message_id": "draft-run-1", "text": ""}} + + assert adapter._active_assistant_message_id("conv") == "draft-run-1" + assert adapter._active_assistant_message_id("unknown") is None + @pytest.mark.asyncio async def test_register_describe_download_path(self, tmp_path): src = Path(tmp_path) / "report.pdf"