Conversation content: Context and Stream

The conversation’s Context lane and Stream for this chat door now read the same collection. All, Context, and Stream filter that collection. Personal Stream remains separate. Ordinary messages are not duplicated into the feed; an authorized Original message action opens provenance.

Each row names its audience: Shared with this chat or Only you. Context posts, pinned Stream entries, and shared inline/reply-tag captures remain conversation content. A direct human session also sees that owner's private message captures and private entries explicitly scoped to the conversation. Agent keys, unified connector calls, delegated calls, and embedded sessions never receive these private rows.

Context replies keep their parent previews and indentation in the descending feed order, including when the parent is on another page. Context editing, deleting, reporting, and explicit Ask agents remain available. Private entry creates a private scoped Stream entry; it does not post or pin. Stream entries retain edit/delete, tag search, pin/unpin, and authorized original-message actions. The entry editor restricts shared hashtag suggestions to this conversation; other people’s private captures never contribute. Sharing a private entry uses a separate Share & pin preview. The client submits the reviewed text, and the pin transaction locks the owner, conversation and entry, checks live membership, and rejects changed text with 409. POST /api/thoughts/:id/pin requires {conversationId,expectedText} with the exact reviewed text (at most 20,000 characters); clients omitting expectedText receive 400 and must be updated before pinning.

GET /api/chat/conversations/:conversationId/content accepts filter=all|context|stream, case-insensitive search (up to 200 characters), limit (1–100), and an opaque cursor. Each page rechecks live membership and API-key read scope/expiry/revocation. Responses are Cache-Control: no-store.

Response: {items,contextAvailable,nextCursor?}. When Context is disabled, this endpoint still returns authorized Stream entries, with contextAvailable:false; the UI hides the Context filter and composer while keeping private Stream entry creation available. Each item has the existing source id, createdAt, origin, visibility, provenance, sourceAliases, and either a context projection or thought projection. No source storage migration or copied identity is introduced. Ordering is descending creation time then source ID. Legacy shared hashtag capture duplicates within identical ownership/audience are grouped before pagination and expose their IDs as aliases. Pinned items retain their own actionable pin identity. Cursor scope binds the exact account, conversation, filter, search and private visibility and effective Context availability; it never grants access. Inaccessible or deleted original messages have no source IDs in the projection. Soft-deleted Stream entries are omitted; Context deletions remain bodyless tombstones for replies.

The unified UI stores reads only in a mounted account/conversation session. Private Stream rows never enter the shared Context manager. Account/conversation replacement hides old rows synchronously, stale responses are discarded, permission failures clear loaded rows, and network errors retain clearly labeled prior content. Stream content socket events trigger an authorized reread rather than inserting a payload directly; viewer-only display updates use thought:collapsed as described below. Context retains quiet polling and foreground refresh.

The additive read-only MCP tool oc_list_conversation_content accepts the same conversation and query parameters. The shared Ideaflow connector exposes it as openchat__oc_list_conversation_content under openchat:read; these agent reads exclude private rows.

Intention tracking is described in Context intention lifecycle. Explicit webhook setup is described in Context webhooks.

Tests: conversationContent.integration.test.ts uses an isolated Neo4j fixture for mixed pagination, aliases, private visibility, key revocation, source redaction, reviewed pin races and membership loss. conversationContent.route.test.ts covers actor gating. Mobile tests cover the compatibility Stream door, search/tag/socket refresh, private entry creation and sharing, stale preview rejection, account replacement, permission loss and unchanged filter selection.

Stream entries carry collapsed, the reading user's own preference, set with PUT /api/thoughts/:id/collapsed {collapsed} on any entry the user can read (own, pinned to a chat they belong to, or a tag capture from such a chat); it is stored as (:User)-[:COLLAPSED_THOUGHT]->(:Thought) and emits thought:collapsed to that user only. For user-facing search and display controls, see Messages and Stream.

Agent access does not grant permission to approve private sharing, change intention lifecycle, or configure webhook destinations. Those actions require the owner's direct signed-in session.