From 43a4a8bdc0c9b74bab7232cb541a068788598e30 Mon Sep 17 00:00:00 2001 From: witbrock Date: Fri, 11 Sep 2026 12:09:16 -0400 Subject: [PATCH] Open task references in shared panel and add message reference menus (JVNAUTOSCI-2742) --- .../integrations/internal_mcp/catalogue.py | 5 +- src/backend/server/routes/message_routes.py | 1 + src/backend/services/message_service.py | 29 +++++++-- .../web/von_interface/static/js/chatTab.js | 2 +- .../static/js/components/messagePanel.js | 55 ++++++++++++++++- .../static/js/components/taskPanel.js | 60 +++++++++++++++++-- .../static/js/utils/messageStreamReference.js | 21 +++++++ .../js/utils/selectConceptByIdHandler.js | 11 +++- .../test_internal_mcp_direct_message_tools.py | 17 ++++++ tests/backend/test_message_service.py | 34 +++++++++++ .../frontend/messageStreamNavigation.test.js | 49 +++++++++++++++ .../frontend/selectConceptByIdHandler.test.js | 13 ++++ tests/frontend/taskPanelReference.test.js | 16 +++++ 13 files changed, 296 insertions(+), 17 deletions(-) create mode 100644 src/frontend/web/von_interface/static/js/utils/messageStreamReference.js create mode 100644 tests/frontend/messageStreamNavigation.test.js create mode 100644 tests/frontend/taskPanelReference.test.js diff --git a/src/backend/integrations/internal_mcp/catalogue.py b/src/backend/integrations/internal_mcp/catalogue.py index fcea74e6f..8a48a202f 100644 --- a/src/backend/integrations/internal_mcp/catalogue.py +++ b/src/backend/integrations/internal_mcp/catalogue.py @@ -6875,6 +6875,7 @@ def _message_list_direct(**kwargs): thread_id=kwargs.get("thread_id"), limit=limit, skip=offset, + other_user_id=kwargs.get("other_user_concept_id"), ) projected = [project_direct_message(message) for message in messages] return { @@ -13094,6 +13095,7 @@ def _message_list_direct_input_schema() -> Schema: "include_sent": (bool, type(None)), "include_received": (bool, type(None)), "thread_id": (str, type(None)), + "other_user_concept_id": (str, type(None)), "limit": (int, type(None)), "offset": (int, type(None)), "acting_user_concept_id": (str, type(None)), @@ -13102,7 +13104,8 @@ def _message_list_direct_input_schema() -> Schema: }, allow_unknown=False, description=( - "List the authenticated actor's sent and received internal direct messages." + "List the authenticated actor's sent and received internal direct messages, " + "optionally narrowed to a conversation with other_user_concept_id." ), ) diff --git a/src/backend/server/routes/message_routes.py b/src/backend/server/routes/message_routes.py index 56a2ca067..bb095233e 100644 --- a/src/backend/server/routes/message_routes.py +++ b/src/backend/server/routes/message_routes.py @@ -875,6 +875,7 @@ def list_threads() -> ResponseReturnValue: { "threads": threads, "count": len(threads), + "current_user_id": user_id, } ), 200, diff --git a/src/backend/services/message_service.py b/src/backend/services/message_service.py index 5c7a43c7a..d4a7e921b 100644 --- a/src/backend/services/message_service.py +++ b/src/backend/services/message_service.py @@ -337,11 +337,7 @@ def relationship_values(predicate: str) -> List[str]: "content": concept_data.get("content_fallback"), "sent_at": concept_data.get("sent_at"), "status": concept_data.get("message_status"), - "read_by": [ - item - for item in raw_read_by - if isinstance(item, str) - ], + "read_by": [item for item in raw_read_by if isinstance(item, str)], "thread_id": ( relationship_values(PREDICATE_THREAD)[0] if relationship_values(PREDICATE_THREAD) @@ -429,7 +425,9 @@ def create_message( relationships[PREDICATE_REPLY_TO] = [reply_to_id.strip()] metadata_payload = metadata if isinstance(metadata, dict) else {} - metadata_payload = {str(k): v for k, v in metadata_payload.items() if isinstance(k, str)} + metadata_payload = { + str(k): v for k, v in metadata_payload.items() if isinstance(k, str) + } attribution = metadata_payload.get("attribution") if not isinstance(attribution, str) or not attribution.strip(): attribution = f"Sent by Von on behalf of {sender_id}" @@ -745,6 +743,7 @@ def get_messages_for_user( thread_id: Optional[str] = None, limit: int = 50, skip: int = 0, + other_user_id: Optional[str] = None, ) -> List[Dict[str, Any]]: """Get messages for a user (sent and/or received). @@ -785,6 +784,24 @@ def get_messages_for_user( if thread_id: base_filter[f"relationships.{PREDICATE_THREAD}"] = thread_id.strip() + if other_user_id: + # Further narrow the actor's existing mailbox; never replace its scope. + counterpart = other_user_id.strip() + base_filter["$and"] = [ + { + "$or": [ + { + f"relationships.{PREDICATE_SENDER}": user_id, + f"relationships.{PREDICATE_RECIPIENT}": counterpart, + }, + { + f"relationships.{PREDICATE_SENDER}": counterpart, + f"relationships.{PREDICATE_RECIPIENT}": user_id, + }, + ] + } + ] + query = apply_concept_query_filter(base_filter) cursor = coll.find(query).sort("created_at", -1).skip(skip).limit(limit) diff --git a/src/frontend/web/von_interface/static/js/chatTab.js b/src/frontend/web/von_interface/static/js/chatTab.js index 0acd0264a..ac58c7079 100644 --- a/src/frontend/web/von_interface/static/js/chatTab.js +++ b/src/frontend/web/von_interface/static/js/chatTab.js @@ -28181,7 +28181,7 @@ function populateChatSessionMenu(items) { return menu; } -function openChatSessionMenu(x, y, items, options = {}) { +export function openChatSessionMenu(x, y, items, options = {}) { if (!Array.isArray(items) || items.length === 0) { return; } diff --git a/src/frontend/web/von_interface/static/js/components/messagePanel.js b/src/frontend/web/von_interface/static/js/components/messagePanel.js index c5c30172d..113aadd83 100644 --- a/src/frontend/web/von_interface/static/js/components/messagePanel.js +++ b/src/frontend/web/von_interface/static/js/components/messagePanel.js @@ -10,6 +10,8 @@ import { getSessionScopedOrgId } from '../utils/sessionScopedStorage.js'; import { hydrateConceptCartouchesInRoot } from '../utils/selectConceptByIdHandler.js'; import { cartouchifyElementText } from '../utils/textDecorator.js'; import { showToast } from '../utils/toast.js'; +import { buildMessageStreamReference } from '../utils/messageStreamReference.js'; +import { copyTextWithClipboardFallback } from '../utils/copyJsonButtonState.js'; import { clearRecommendationReviewResults, renderRecommendationReviewPayload, @@ -136,7 +138,8 @@ function renderMessagesTabContent() {