@memberjunction/ng-conversations
A comprehensive Angular component library for building conversation-based interfaces in MemberJunction, including messaging, artifact management, collections, projects, tasks, agent interaction panels, and collaboration features.
Layering note. The orchestration logic (agent dispatch, default-agent resolution, mention parsing, bridge state, streaming, client tools, sessions observability) lives in
@memberjunction/conversations-runtime— a pure-TS, framework-agnostic package. This widget is one consumer of the runtime; React/Vue/Node hosts are also intended consumers. The widget is automatically wired to the runtime viaConversationsRuntimeBootstrap(registeredprovidedIn: 'root'), which injects adapters for notifications, active-task tracking, and realtime sessions. Seeguides/CONVERSATIONS_UX_STACK_GUIDE.mdfor the full architecture.
Customization without forking
Section titled “Customization without forking”The widget exposes three layers of extension:
| Surface | What it lets you do |
|---|---|
8 named slots (mjChatSlot directive) | Replace the header, emptyState, agentPresence, messageRenderer, messageExtra, or demonstrationSurface regions with your own templates — or ADD to the default header with headerActions (renders inside the default header’s action strip, after the stock buttons; suppressed when a full header replacement is projected), or above the composer with composerExtra. Three consumption modes: project an ad-hoc template, wrap the exported default for containment, or subclass the default. |
| Before/After cancelable events | (beforeAgentTurn), (beforeToolInvoked), (beforeResponseFormSubmitted) let you observe AND veto (event.Cancel = true) before the action runs. (beforeAgentTurn) fires once per agent turn, before any reply row exists, and can also send the turn to another agent (event.RedirectAgentId). Plus informational (sessionStarted) / (sessionChannelStateChanged) / (sessionEnded) for realtime lifecycle. |
--mj-chat-* design tokens | Override bubble colors, composer chrome, character accents, and voice-state hues via standard CSS custom-property overrides. Defaults adapt to dark mode through semantic --mj-* tokens. |
Slot interfaces + cloneable default components + the ChatSlotDirective are exported — see the public API.
Overview
Section titled “Overview”The @memberjunction/ng-conversations package is a large, feature-rich module that powers MemberJunction’s conversation UI. It provides 40+ components covering the entire conversation lifecycle: message composition and rendering (with markdown, mentions, code blocks, and artifacts), conversation navigation and history, threaded discussions, artifact collections and libraries, project/task management, agent execution panels, sharing/permission modals, search, notifications, and export.
graph TD
A[ConversationsModule] --> B[Messaging]
A --> C[Navigation]
A --> D[Collections & Library]
A --> E[Agent & Tasks]
A --> F[Collaboration]
B --> B1[MessageItemComponent]
B --> B2[MessageListComponent]
B --> B3[MessageInputComponent]
B --> B4[MentionEditorComponent]
B --> B5[SuggestedResponsesComponent]
B --> B6[ConversationMessageRatingComponent]
C --> C1[ConversationWorkspaceComponent]
C --> C2[ConversationNavigationComponent]
C --> C3[ConversationSidebarComponent]
C --> C4[ConversationListComponent]
C --> C5[ConversationChatAreaComponent]
D --> D1[CollectionTreeComponent]
D --> D2[CollectionViewComponent]
D --> D3[LibraryFullViewComponent]
D --> D4[ArtifactCreateModalComponent]
E --> E1[AgentProcessPanelComponent]
E --> E2[ActiveAgentIndicatorComponent]
E --> E3[TasksFullViewComponent]
E --> E4[GlobalTasksPanelComponent]
F --> F1[ShareModalComponent]
F --> F2[MembersModalComponent]
F --> F3[ExportModalComponent]
F --> F4[SearchPanelComponent]
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#7c5295,stroke:#563a6b,color:#fff
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
style D fill:#b8762f,stroke:#8a5722,color:#fff
style E fill:#7c5295,stroke:#563a6b,color:#fff
style F fill:#2d8659,stroke:#1a5c3a,color:#fff
Installation
Section titled “Installation”npm install @memberjunction/ng-conversationsImport the Module
Section titled “Import the Module”import { ConversationsModule } from '@memberjunction/ng-conversations';
@NgModule({ imports: [ConversationsModule]})export class YourModule { }Full Conversation Workspace
Section titled “Full Conversation Workspace”The top-level workspace component provides a complete conversation experience with sidebar, chat area, and thread panel:
<mj-conversation-workspace [conversationId]="selectedConversationId" (conversationChanged)="onConversationChanged($event)"></mj-conversation-workspace>Chat Area
Section titled “Chat Area”The chat area handles message display, input, and agent interactions:
<mj-conversation-chat-area [conversationId]="conversationId" [conversation]="conversation" [isNewConversation]="isNewConversation" (conversationCreated)="onConversationCreated($event)"></mj-conversation-chat-area>Feature toggles
Section titled “Feature toggles”Embedding products (white-labeled end-user apps, embedded widgets) can pare the chat surface down through the component contract — no CSS overrides on internal class names. All default to true, so existing consumers are unaffected. Set to false to remove the feature entirely (the affordance is not rendered, not merely disabled):
| Input | Gates |
|---|---|
allowMentions | MASTER switch for the composer’s mention/command triggers (@ agents, # entities, / skills). When false, all are off regardless of the per-type flags below |
allowAgentMentions | The @ agent/user mention trigger (under allowMentions). Set false on a surface pinned to a default agent that wants / skills but not @ — an @ overrides the pinned agent in message routing |
allowEntityMentions | The # entity/query mention trigger (under allowMentions) |
allowSkillCommands | The / skill-command trigger (under allowMentions) |
allowAttachments | The file-attachment button |
allowPlanMode | The composer’s Plan Mode toggle button |
allowRealtime | The composer’s real-time voice (co-agent) launcher button |
AllowForking | Starting forks: Fork from here, and Edit or Regenerate as a fork. When false, Edit and Regenerate change the latest turn in place, as without the Conversations: Fork authorization. UI only; deny that authorization to a role to block forks on the server too |
AllowStopRun | Stopping and continuing agent runs: the composer’s Stop and Stop all, the Stop pill on an in-progress reply, the Continue button on a stopped reply, and the “Continue, or tell {agent} what to do differently” placeholder. UI only: a run stopped from another client still shows as stopped, and the next message still continues it. The composer’s status line (“Sage is working, 0:41”) is not part of this cap; it shows for any reply in flight |
showEmptyFill | The message list’s built-in “No messages yet” filler |
showLoadingState | The centered loading spinner (loading still short-circuits rendering, so hiding it does not flash the empty state) |
showAgentPicker | The agent picker |
showAgentModePicker | The agent-mode picker |
showExportButton | The conversation export button |
showShareButton | The conversation share button |
showArtifactIndicator | The artifact indicator |
showAgentRunDetails | The per-message agent run-details section — the expander header AND its grid (run ID, step/token counts, $ cost) — developer/observability data most end-user surfaces hide. The gear button that opens the panel is itself hidden when nothing would be left in it (no run details, no associated tasks, and no delete/pin/rating overflow), so an end-user surface gets no dead control rather than one that opens an empty popup |
showReactions | The per-message reaction buttons (like / comment) |
showMessageRating | The per-message thumbs rating control |
allowPinning | Message pinning (per-message pin button, header pin chip, pinned-messages panel) |
allowMessageEdit | The per-message edit button (owner’s own messages) |
allowMessageDelete | The per-message delete button (owner’s own messages) |
showSuggestedPrompts | The empty-state’s built-in suggested-prompt chips |
showDateNavigation | The message list’s sticky date header + jump-to-date navigation |
Behaviour switches
Section titled “Behaviour switches”Unlike the gates above, these change how the surface behaves and default to today’s behaviour:
| Input | Default | Effect when set |
|---|---|---|
readReplyFromTop | false | A finished turn — the reader’s message plus the reply — is scrolled to the top of the pane if it is taller than the pane, so the run ends at the start of the answer instead of its end. A turn that fits stays where it is; a reader who scrolled up during the run is never moved. (Independent of this switch, an in-place message update — progress, status, streamed text — only follows the tail for a reader who is already at the bottom.) |
<!-- e.g. a minimal, single-agent end-user surface --><mj-conversation-chat-area [conversationId]="conversationId" [allowPlanMode]="false" [allowRealtime]="false" [showEmptyFill]="false" [showAgentPicker]="false"></mj-conversation-chat-area>Header customization
Section titled “Header customization”Beyond the boolean toggles, the header’s Export button is re-brandable and the action strip is extensible:
exportButtonLabel(default'Export') andexportButtonIcon(default'fas fa-download') — relabel/re-icon the Export button (e.g. a white-label “Download”).- The
headerActionsslot adds host buttons INSIDE the default header’s action strip, after the stock buttons — unlike theheaderslot, which replaces the whole header (and therefore suppressesheaderActions). Context carries the conversation, its ID, andisProcessing. UsemjButton variant="flat" size="sm"for visual parity:
<mj-conversation-chat-area [conversationId]="conversationId"> <ng-template mjChatSlot="headerActions" let-conversation let-isProcessing="isProcessing"> <button mjButton variant="flat" size="sm" [disabled]="isProcessing" (click)="printConversation(conversation)"> <i class="fas fa-print"></i> <span class="btn-label">Print</span> </button> </ng-template></mj-conversation-chat-area>Branded export
Section titled “Branded export”Branding applies to every export format. HTML gets the full treatment (theme colors, an inlined logo, the title, and a trademark footer); JSON/markdown/text carry the title and trademark, markdown also references the logo, and JSON emits a branding block. Supply exportBranding on the chat area (forwarded to the export modal, where it defaults the “Include branding” checkbox on):
// ExportBranding (from the export service){ brandTokens?: Record<string, string>; // HTML only: ':root{}' block baked into the file; when omitted // and includeTheme is on, DEFAULT_EXPORT_THEME_TOKENS are // auto-snapshotted from the live document at export time logoUrl?: string; // HTML: inlined as a data URI above the title (raw URL on fetch // failure); markdown: referenced by URL; JSON: carried as a string title?: string; // overrides the document title (defaults to the conversation name) trademark?: string; // short attribution line rendered at the foot of every format // (styled HTML footer, markdown/text trailer, JSON branding field)}Color theming (brandTokens) is HTML-only — it has no meaning in the data/plain-text formats, and those formats never serialize the tokens. Without branding the exported file is unchanged from previous releases: the HTML stylesheet reads var(--mj-…, legacyHex) so every color resolves to the exact prior value when no :root block is emitted, and JSON/markdown/text are byte-identical to before. Programmatic consumers can build export content without a download via ExportService.BuildExportContent(...) and snapshot the live theme via ExportService.SnapshotBrandTokens().
Two gating rules are worth knowing:
includeCSSis an HTML-only concern. In HTML the logo and the trademark footer each need their stylesheet rule, so both are suppressed whenincludeCSSis false — an unstyled full-size logo is worse than none. Thetitleoverride has no stylesheet dependency and always applies. Markdown/text/JSON ignoreincludeCSSentirely.- The theme snapshot captures a CHOSEN mode, not the live one.
ExportOptions.themeMode('light'|'dark', default'light'; surfaced as a Theme dropdown in the modal) decides which palette is baked in. Without it, exporting from a dark session put dark text on the export’s white page. The exportedbodycarries--mj-bg-pageand--mj-text-primary, so a dark export is coherent rather than inverted. includeThemegates only the token AUTO-SNAPSHOT. An explicitly suppliedbrandTokensmap, pluslogoUrl/title/trademark, apply wheneverbrandingis present. In the modal, “Include branding” is the single user-facing switch for all of it, and it sits with the general options because it affects every format — not just HTML.
trademark and title are not markdown-escaped; a value containing _, ], ), or a newline can break the surrounding markdown. Supply plain text (the HTML and JSON paths are escaped).
Chats with several people
Section titled “Chats with several people”A host that puts several people in one conversation (a team room, a shared chat) sets when agents answer through these inputs, without forking the chat area. Each defaults to today’s behavior:
| Input | Default | Effect when set |
|---|---|---|
AgentReplyMode | 'Always' | 'MentionOnly' starts an agent turn only for a message that tags an agent. Any other message is posted with no turn: no reply row, no placeholder, no turn events |
AllowedAgentIDs | null (every agent) | The agents that may answer. Narrows the @ list, every route (a tagged agent, continuity, the pinned and host default agents, the conversation manager), the manager’s delegation, including each agent step of a workflow it plans, and the pin and voice pickers. An empty list allows none |
MentionPeople | null (the current user only) | The people the @ list offers, such as the chat’s members (MentionPerson from @memberjunction/conversations-runtime). Each composer keeps its own list |
AgentHistoryFrom | null (the whole conversation) | The first moment of the conversation an agent turn may read. The server loads the agent’s history from there, uses no summary of earlier messages, and holds the same floor in the run’s other reads of the conversation. Sent only when set, so leaving it null keeps working against an older MJAPI |
AgentTurnHandler | null (MJ’s own path) | An async hook that runs each turn on the host’s server. The chat area calls it once per turn, after BeforeAgentTurn and before any reply row exists, and shows the rows it reports. A reply it reports as In-Progress is followed like any other in-progress reply. The host publishes that row’s live status with AgentRunStatusPublisher from @memberjunction/server, on the caller’s session: significant-step progress, streamed text, the partial result, and the completion, with the reply row’s id on the completion. When the turn throws instead of returning a result, call PublishFailure with the reply row’s id and the error |
AutoNameConversation | true | false stops MJ naming a new conversation from its first message or first voice utterance |
Routing picks one agent per turn, in this order: a tagged agent, then (under 'Always' only) the last agent that answered other than the conversation manager, the conversation’s pinned agent, the host’s DefaultAgentId, and the conversation manager. An agent AllowedAgentIDs leaves out is skipped. BeforeAgentTurn then fires once, carrying the resolved AgentId, AgentName, Route and UserMessageId: a listener can cancel the turn, which then writes nothing more, or set RedirectAgentId to another allowed agent.
Read-only
Section titled “Read-only”ReadOnly hides every composer and shows a banner. ReadOnlyMessage is that banner’s text; leave it null to use the View-share sentence. EffectiveReadOnly is true when the host set ReadOnly or the conversation is shared with View access. A View share with ReadOnly left false still shows today’s disabled composer.
Pin, edit, and delete on the message list, the mode picker, the agent picker, suggested responses, project assignment, and unpinning all follow EffectiveReadOnly. Each mj-message-input the chat area renders gets [ReadOnly]="EffectiveReadOnly", so send, the initial auto-send, and starting realtime refuse. Ratings close too: canEdit is false while that message is read-only. The pins panel’s AllowUnpin is bound to !EffectiveReadOnly. With it false the panel still lists pins and still jumps, and it hides Unpin.
A reply the host reports as In-Progress is followed as soon as the chat area takes the row. If that turn already finished, the completion waiting in the replay window is applied then, so the row does not stay in progress until the conversation is opened again.
The composerExtra slot renders host UI directly above the composer, wherever the chat area shows one (the new-conversation composer and the empty state’s included). Its context is IMJChatComposerExtraContext: the conversation as $implicit, plus ConversationId and IsProcessing.
<mj-conversation-chat-area [conversationId]="conversationId" AgentReplyMode="MentionOnly" [AllowedAgentIDs]="spaceAgentIds" [MentionPeople]="members" [AgentHistoryFrom]="historyFloor" [AgentTurnHandler]="runTurnOnServer" [AutoNameConversation]="false" (BeforeAgentTurn)="onBeforeAgentTurn($event)"> <ng-template mjChatSlot="composerExtra" let-busy="IsProcessing"> <div class="audience-line">Everyone in this chat sees your message</div> </ng-template></mj-conversation-chat-area>Assistant identity
Section titled “Assistant identity”White-label hosts can brand the AI side of the message feed through the component contract (no CSS on .message-sender / .avatar-circle internals). Both default to null — the engine-resolved agent identity, today’s behavior:
| Input | Overrides |
|---|---|
assistantDisplayName | The display name on AI messages (e.g. a per-tenant persona like “Betty the Teacher”). Display-only: internal logic such as the conversation-manager check keeps comparing the real agent name |
assistantAvatarUrl | The AI message avatar — an image replaces the agent’s Font Awesome icon |
These complement agentCharacterConfig, which drives only the agentPresence strip — the two inputs extend the same identity into every message bubble. Runtime changes propagate to already-rendered messages (branding configs that resolve after first render, per-conversation persona switches), and a broken/whitespace avatar URL degrades to the agent icon rather than a broken-image glyph.
Scope — what the override deliberately does NOT cover: the run-details expander header and its record link (they describe the real agent’s diagnostics), the role tooltip (the agent record’s Description), and realtime session cards. Hosts needing those surfaces hidden from end users gate them with showAgentRunDetails / their own chrome rather than relabeling internal data.
Chat Overlay
Section titled “Chat Overlay”A floating chat panel (bottom-right corner) that wraps the chat area for persistent agent access across the application. Collapses to a bubble icon, expands to a full chat panel.
<mj-chat-agents-overlay [IsVisible]="!isChatRoute" (ToolExecuted)="onToolExecuted($event)" (OpenEntityRecord)="onOpenRecord($event)" (OpenFullWorkspace)="onExpandToWorkspace($event)"></mj-chat-agents-overlay>The overlay is generic — it raises events for navigation and tool execution. The consuming application (e.g., MJExplorer) handles those events with app-specific logic like NavigationService.OpenEntityRecord().
Related packages:
@memberjunction/ai-agent-client— Core agent SDK (framework-agnostic, GraphQL transport, tool registry)@memberjunction/ng-agent-client— Angular wrapper for the agent SDK@memberjunction/core-entities— ConversationEngine for centralized conversation data
Message Components
Section titled “Message Components”Message List
Section titled “Message List”<mj-message-list [messages]="conversationMessages" [isLoading]="isLoadingMessages" (messageRated)="onMessageRated($event)"></mj-message-list>Message Input with Mentions
Section titled “Message Input with Mentions”<mj-message-input [conversationId]="conversationId" [allowSend]="!isProcessing" (messageSent)="onMessageSent($event)"></mj-message-input>Mention Editor
Section titled “Mention Editor”<mj-mention-editor [mentionSources]="availableMentionSources" (mentionSelected)="onMentionSelected($event)"></mj-mention-editor>Collections and Library
Section titled “Collections and Library”<!-- Collection tree for organizing artifacts --><mj-collection-tree [projectId]="currentProjectId" (collectionSelected)="onCollectionSelected($event)"></mj-collection-tree>
<!-- Full library view --><mj-library-full-view [projectId]="currentProjectId"></mj-library-full-view>Project and Task Management
Section titled “Project and Task Management”<!-- Project selector --><mj-project-selector [currentProjectId]="projectId" (projectChanged)="onProjectChanged($event)"></mj-project-selector>
<!-- Tasks view --><mj-tasks-full-view [projectId]="currentProjectId"></mj-tasks-full-view>Agent Components
Section titled “Agent Components”<!-- Agent execution panel --><mj-agent-process-panel [agentRunId]="activeRunId"></mj-agent-process-panel>
<!-- Active agent indicator --><mj-active-agent-indicator [isActive]="agentIsRunning"></mj-active-agent-indicator>Real-Time Voice (Co-Agent) UX
Section titled “Real-Time Voice (Co-Agent) UX”The package hosts the full client UX for MJ’s real-time co-agent sessions — live voice calls with the conversation’s agent, with interactive channel surfaces (the live Whiteboard) docked beside the call. Architecture background: guides/REALTIME_CO_AGENTS_GUIDE.md.
RealtimeSessionService (services/realtime-session.service.ts) is the provider-agnostic orchestrator, injectable at root. It drives a client-direct session: it calls the StartRealtimeClientSession mutation to mint a server-scoped ephemeral credential, resolves the matching BaseRealtimeClient driver (from @memberjunction/ai-realtime-client — OpenAI, Gemini, ElevenLabs, or AssemblyAI) through the ClassFactory by the server-reported Provider key, and connects the browser directly to the realtime provider — audio frames never transit the MJ server; only tool calls, final transcripts, channel state, and usage telemetry relay back over GraphQL. It exposes the reactive session state hosts consume: ConnectionState$ (connecting | listening | speaking | thinking | error | closed), Captions$, Active$, DelegationProgress$ / DelegationResult$ / DelegationNarration$, ActiveChannels$, ChannelFocus$, Minimized$, SavingRecording$ (true from the start of a recorded call’s teardown until the recording upload settles; the service turns it into a browser beforeunload prompt so closing the tab mid-upload asks “Leave site?”, on desktop browsers only), plus SendText (typed input into the live call), ToggleMute, RegisterClientToolHandler (prefix-routed, client-executed UI tools), and the explicit delegation cancel channel — CancelDelegation(callId) / CancelInFlightDelegations() call the CancelRealtimeSessionTool mutation and flip the card to “Cancelled by user” (deliberate policy: true barge-in never aborts delegated work — only the per-card ✕ does). The client driver’s OnUsage token deltas are accumulated and relayed onto the co-agent’s observability prompt run via RelayRealtimeUsage, debounced (10 s) plus a teardown flush.
Co-agent selection, pairing constraints & authorization-gated overrides: the composer’s voice picker (mj-voice-agent-picker, opened by the caret next to the phone button — or automatically on a conversation with no prior agent participation) lets every user choose which co-agent (ACTIVE Realtime-type agent, from the same run-permission-filtered cache the @mention routing uses) fronts the call when more than one exists; the choice rides the mint’s coAgentId and persists cross-device via UserInfoEngine (mj.realtimeVoice.coAgent.v1), so the friction-free instant start honors it too. A co-agent with rows in MJ: AI Agent Paired Agents may only front its paired targets — the picker constrains the target list to those rows (Sequence order, IsDefault preselected; pure helpers in services/voice-pairing.ts) — while a co-agent with zero rows stays universal (today’s flow untouched; pairings constrain a chosen co-agent, they never mandate one). The voice-model selector (and any future session-config override, carried as the mint’s configOverridesJson, e.g. {"realtime":{"modelPreference":…}}) renders only for users holding the Realtime: Advanced Session Controls MJ Authorization — evaluated client-side by the pure UserHoldsAuthorization helper (services/user-authorization.ts) from cached metadata as a disclosure gate; the server independently enforces the authorization on the mint, and unauthorized users silently get server defaults. (The gear popover’s interface-density control is UX disclosure, not session config — it is not gated.)
The call overlay (components/realtime/): RealtimeSessionOverlayComponent (mj-realtime-session-overlay) fills the conversation panel in place while a session is active — hosted by ConversationChatAreaComponent behind Active$, started from the mic button in MessageInputComponent. Two columns:
- Main column — the unified APP-BAR (
RealtimeAgentBannerComponent: identity + turn state + model name + the disclosure-gated action cluster — captions, the gear popover hosting the interface-density escape hatch and developer links, minimize, End call; in review the Start-live + Close actions live here too), then the pure-audio hero orb OR the unified session thread (RealtimeSessionThreadComponent, fed by the sharedRealtimeSessionStatemerge of caption/delegation/narration streams), the channel strip, and the bottom dock (RealtimeComposerComponent: phone-call strip at low disclosure levels ⇄ fused mute/captions minis + typed composer at level 2+; typed turns behave identically to spoken ones). - Right panel —
RealtimeSurfaceTabsComponent, the tabbed surface panel (RealtimeSurfaceTabsModelis the framework-free, unit-tested tab state): channel tabs lead the strip, then one tab per artifact a delegated run produces (added UNFOCUSED with a persistent violet “unseen” glow until visited — content never steals the screen), with the Activity rail pinned LAST.
Progressive disclosure (realtime-disclosure.ts): the console grows with the user. A first-ever call is PURE AUDIO — a breathing hero orb, mute / Details / End, nothing to read; the caption thread, composer dock, surface panel and gear unlock by level (0–4) as the user acts (the hero’s “Show the conversation”, the T-to-type hotkey, the Details peek) or across sessions via the per-user milestones ratchet (UserInfoEngine, mj.realtimeVoice.uxMilestones.v1; the gear’s Simple/Standard/Pro/Auto density control is the manual escape hatch). Content never flips the console open — the one auto-reveal is a channel’s first agent activity (RealtimeSessionService.ChannelActivity$): the panel opens as a peek with that channel’s tab focused + flashed, while the left column stays exactly as it was. Review mode bypasses disclosure entirely.
Audio-reactive visuals (realtime-audio-visuals.ts): when the active driver meters its audio planes (BaseRealtimeClient.GetAudioActivity() — all four current drivers do, both directions), the overlay samples it on a requestAnimationFrame loop outside Angular and writes CSS variables directly: the hero orb scales with the smoothed output envelope (speaker-cone attack/decay), the EQ bars render the true 9-bin spectrum, and the visuals recolor by speaking direction (agent = brand, user = green) with hysteresis so syllable gaps never flicker. Un-metered drivers gracefully keep the turn-state-driven animations. See the guide’s §11 for the full pipeline.
Interactive channels are plugins — the shell is channel-agnostic. BaseRealtimeChannelClient (@memberjunction/realtime-runtime, src/channels/base-realtime-channel-client.ts) is the contract: a client-executed tool set declared to the realtime model at session mint, a perception serializer feeding coalesced state deltas into the model as context notes, a dynamically-created Angular surface component the plugin binds itself, a persisted state of record, prior-session restore (RestoreState), artifact snapshots (SaveAsArtifact), and focus-mode layout requests. Plugins resolve at session start from the MJ: AI Agent Channels registry by ClientPluginClass key.
Channel contract v2. Every channel describes itself with a RealtimeChannelDescriptor (GetDescriptor(); see @memberjunction/ai-core-plus): its nouns, its verbs (parameter schemas, InvokableBy), the events it emits, its display policy ('open-on-start' | 'on-demand' | 'headless'), its default availability ('all-sessions' | 'opt-in') and the most the model may perceive of it ('none' | 'state' | 'pixels'). A channel that implements only the original members is synthesized a descriptor (verbs from its tools, conservative defaults), so nothing changes for existing channels; the Whiteboard, Remote Browser, Media and Client Context channels ship authored descriptors with their native tool names unchanged. Which channels a session gets is decided by scoping (code default → agent → app → host-declared) with the registry’s IsActive = false as a master kill switch; see @memberjunction/realtime-runtime for the runtime half. The headless Client Context channel’s ContextTool proxy now accepts target: { channel, instance? }: a targeted call is validated by the runtime against the addressed verb’s schema (a malformed call returns a structured, model-recoverable error listing each violation and what would have been valid) and an on-demand channel is opened with action: "open". The Whiteboard’s change-driven frame pump (leading edge, trailing settle, dedupe, negotiated cadence, one confirmation frame after an agent edit) now lives in the base channel (EnableVisualPerception), with frames tagged by the change id of the state they describe.
The live Whiteboard is a thin consumer of @memberjunction/ng-whiteboard — the board itself (the WhiteboardState engine, the Whiteboard_* tool API, the host/board/toolbar/zoom/popover/snapshot components, exports, the sandboxed-HTML-widget input bridge, the context menu) lives in that generic package; read its README for whiteboard details. This package contributes only the integration glue (components/realtime/whiteboard/): RealtimeWhiteboardChannel, the ~200-line channel plugin that declares WHITEBOARD_TOOL_DEFINITIONS to the model, routes Whiteboard_* calls to the bound host (or the pure engine call when the pane is collapsed), pipes the coalesced SceneDelta stream into the model as [whiteboard] context notes (with do-not-narrate-minor-edits etiquette inline), forwards widget submissions (MJWhiteboard.submit — the tutoring loop) and agent-undo events, persists/restores the board as the channel’s state of record, and snapshots it to versioned MJ: Artifacts; plus WhiteboardArtifactViewerPlugin (mj-whiteboard-artifact-viewer), the saved-board artifact viewer rendered through the package’s read-only snapshot component.
Session review & resume carryover: a past session replays through the same overlay in review mode — ConversationChatAreaComponent.OpenRealtimeSessionReview(agentSessionId) loads a RealtimeSessionReview via RealtimeSessionReviewService. The loader is chain-aware: a session resumed via lastSessionId chains legs, and the loader walks the chain backwards (capped, cycle-guarded), rendering every leg chronologically with a divider between legs that carries the previous leg’s CloseReason as a chip; the chain’s conversation-history artifacts load as unfocused artifact tabs, and a read-only Whiteboard tab appears when a board was saved. “Start live session” resumes as a new session chained via lastSessionId — saved channel states restore and the prior legs’ transcript is hydrated into the new model’s prompt server-side. The conversations resource also accepts a realtimeSessionId query param (the deep link the custom MJ: AI Agent Sessions form emits) and opens review mode directly.
This package never navigates (no Router): developer links emit a RealtimeNavigateRequest the host converts onto its openEntityRecord chain, and minimizing the call shows the host’s floating “on call” pill while the session stays live.
Collaboration
Section titled “Collaboration”<!-- Share modal --><mj-share-modal [visible]="showShareModal" [resourceId]="resourceId" [resourceType]="'conversation'" (closed)="onShareClosed()"></mj-share-modal>
<!-- Export modal --><mj-export-modal [visible]="showExportModal" [conversationId]="conversationId" (exported)="onExported($event)"></mj-export-modal>Component Reference
Section titled “Component Reference”Messaging Components
Section titled “Messaging Components”| Component | Selector | Description |
|---|---|---|
MessageItemComponent | mj-message-item | Single message display with markdown, artifacts, and rating |
MessageListComponent | mj-message-list | Scrollable message list with auto-scroll |
MessageInputComponent | mj-message-input | Message input with attachment support |
MessageInputBoxComponent | mj-message-input-box | Core input box with auto-resize |
SuggestedResponsesComponent | mj-suggested-responses | Quick response buttons |
FormQuestionComponent | mj-form-question | Structured form input within conversations |
AgentResponseFormComponent | mj-agent-response-form | Agent-generated form responses |
ActionableCommandsComponent | mj-actionable-commands | Clickable command suggestions |
MentionDropdownComponent | mj-mention-dropdown | @-mention autocomplete dropdown |
MentionEditorComponent | mj-mention-editor | Rich text input with mention support |
ConversationMessageRatingComponent | mj-conversation-message-rating | Message feedback (thumbs up/down) |
Navigation Components
Section titled “Navigation Components”| Component | Selector | Description |
|---|---|---|
ConversationWorkspaceComponent | mj-conversation-workspace | Full workspace layout |
ConversationNavigationComponent | mj-conversation-navigation | Top-level navigation |
ConversationSidebarComponent | mj-conversation-sidebar | Left sidebar with conversation list |
ConversationListComponent | mj-conversation-list | Scrollable conversation history |
ConversationChatAreaComponent | mj-conversation-chat-area | Main chat area |
ConversationEmptyStateComponent | mj-conversation-empty-state | Empty state display |
Collection and Library Components
Section titled “Collection and Library Components”| Component | Selector | Description |
|---|---|---|
CollectionTreeComponent | mj-collection-tree | Hierarchical collection browser |
CollectionViewComponent | mj-collection-view | Collection detail view |
CollectionArtifactCardComponent | mj-collection-artifact-card | Artifact card within collections |
LibraryFullViewComponent | mj-library-full-view | Full library interface |
CollectionFormModalComponent | mj-collection-form-modal | Create/edit collection |
ArtifactCreateModalComponent | mj-artifact-create-modal | Create new artifact |
CollectionsFullViewComponent | mj-collections-full-view | All collections browser |
Project and Task Components
Section titled “Project and Task Components”| Component | Selector | Description |
|---|---|---|
ProjectSelectorComponent | mj-project-selector | Project selection dropdown |
ProjectFormModalComponent | mj-project-form-modal | Create/edit project |
TasksFullViewComponent | mj-tasks-full-view | Full tasks management view (standalone) |
TasksDropdownComponent | mj-tasks-dropdown | Task quick-access dropdown |
TaskWidgetComponent | mj-task-widget | Compact task widget |
GlobalTasksPanelComponent | mj-global-tasks-panel | Global tasks panel |
Agent Components
Section titled “Agent Components”| Component | Selector | Description |
|---|---|---|
AgentProcessPanelComponent | mj-agent-process-panel | Agent execution panel |
ActiveAgentIndicatorComponent | mj-active-agent-indicator | Active processing indicator |
ActiveTasksPanelComponent | mj-active-tasks-panel | Active tasks panel |
Real-Time Voice Components
Section titled “Real-Time Voice Components”| Component | Selector | Description |
|---|---|---|
RealtimeSessionOverlayComponent | mj-realtime-session-overlay | The in-place “call mode” overlay for a live voice session (progressive-disclosure console: pure-audio hero → full two-column; audio-reactive orb/EQ) |
RealtimeAgentBannerComponent | mj-realtime-agent-banner | The unified app-bar: identity + turn-state + disclosure-gated actions (captions, gear popover w/ density + dev links, minimize, End; review Start-live + Close) |
RealtimeSessionThreadComponent | mj-realtime-session-thread | Unified live thread (captions, delegation cards, ephemeral narration) |
RealtimeActivityRailComponent | mj-realtime-activity-rail | Session activity rail (the surface panel’s pinned-last tab) |
RealtimeDelegationCardComponent | mj-realtime-delegation-card | ”Working on it” → result card for a delegated agent run |
RealtimeChannelStripComponent | mj-realtime-channel-strip | Chip-per-channel strip |
RealtimeComposerComponent | mj-realtime-composer | The bottom dock: phone-call strip (levels 0–1: mute / captions / Details peek / End) ⇄ fused minis + typed composer (level 2+) |
RealtimeSurfaceTabsComponent | mj-realtime-surface-tabs | Tabbed surface panel: channel tabs first, glowing unfocused artifact tabs, Activity pinned last (backed by the framework-free, unit-tested RealtimeSurfaceTabsModel) |
WhiteboardArtifactViewerComponent | mj-whiteboard-artifact-viewer | Saved-board artifact viewer (registered as WhiteboardArtifactViewerPlugin); renders via @memberjunction/ng-whiteboard’s snapshot component |
The live whiteboard surface itself (
mj-realtime-whiteboard-host, board, toolbar, zoom, “What the agent sees” popover) ships in@memberjunction/ng-whiteboard; theRealtimeWhiteboardChannelplugin here creates it dynamically in a channel tab.
Utility Components
Section titled “Utility Components”| Component | Selector | Description |
|---|---|---|
ShareModalComponent | mj-share-modal | Resource sharing modal |
MembersModalComponent | mj-members-modal | Members management |
ExportModalComponent | mj-export-modal | Data export modal |
SearchPanelComponent | mj-search-panel | Search across conversations |
NotificationBadgeComponent | mj-notification-badge | Unread notification count |
ActivityIndicatorComponent | mj-activity-indicator | Active processing indicator |
ToastComponent | mj-toast | Toast notification display |
InputDialogComponent | mj-input-dialog | Generic text input dialog |
ImageViewerComponent | mj-image-viewer | Image attachment viewer |
Directives
Section titled “Directives”| Directive | Selector | Description |
|---|---|---|
SearchShortcutDirective | [mjSearchShortcut] | Keyboard shortcut for search |
Key Design Patterns
Section titled “Key Design Patterns”Performance Optimization
Section titled “Performance Optimization”Message components use dynamic component creation (ViewContainerRef.createComponent) instead of Angular template binding to minimize render cycles and improve performance with large message lists.
Windowed transcript
Section titled “Windowed transcript”Opening a conversation loads only the most recent page of the transcript — roughly ten
display items — rather than every MJ: Conversation Details row. Everything older is
represented by an “Earlier messages” sentinel at the top; scrolling it into view prepends the
previous page and holds the reader’s scroll position. Items far from the viewport are
unmounted and replaced by height-holding spacers, so the DOM stays bounded no matter how far
back someone pages.
Hosts do not opt in. It is on by default and requires no configuration. The one thing a host should provide is its scrolling element:
<mj-conversation-message-list [ScrollRoot]="myScrollContainer"> </mj-conversation-message-list>The list’s own container does not scroll — consumers typically wrap it in their own scroller,
and that element is what the sentinel’s IntersectionObserver needs as its root. When
ScrollRoot is omitted the component walks its ancestors to find one, which works but depends
on layout having settled.
ConversationEngine.LoadConversationDetails is no longer what this widget calls on open.
It now calls the additive LoadDetailWindow, which pages on the Sequence column and loads
peripherals (agent runs, ratings, artifacts, avatars) scoped to the returned rows.
LoadConversationDetails remains the full-history API and is unchanged — agents and
server-side callers depend on it returning a complete set, so the windowed path deliberately
never writes into the engine’s detail cache.
Paging is measured in display items, not database rows: a realtime session collapses many rows into one timeline card, and a session is never split across pages.
Post-Turn Artifact Panel Auto-Open, Refresh & Switch (decidePostTurnArtifactPanelAction)
Section titled “Post-Turn Artifact Panel Auto-Open, Refresh & Switch (decidePostTurnArtifactPanelAction)”When an agent turn completes in ConversationChatAreaComponent, the component captures the pre-turn set of non-system output ArtifactIDs (captureVisibleArtifactIdBaseline) before the server turn runs and evaluates decidePostTurnArtifactPanelAction once the newly created/versioned artifacts for that turn are loaded:
- Panel Closed + Brand-New Artifact (
action: 'open-new'): Automatically opens the right-hand Artifact Panel when the completed turn created anArtifactIDthat did not exist in the pre-turn baseline (preserving the intentional behavior that versioning an already-existing artifact while the user has closed the panel does not force it back open). - Panel Open + Currently Viewed Artifact Versioned (
action: 'refresh-current'): IncrementsartifactViewerRefreshTriggerand updatesactiveVersionNumberin place so the open viewer immediately reflects the new version (e.g.,v1→v2). - Panel Open + Different Artifact Created or Versioned (
action: 'switch-artifact'): Automatically switches the open Artifact Panel to display the newly created or newly versioned artifact (activeArtifactId,activeVersionNumber). - Reconnected In-Flight Runs: When reconnecting to an already-running turn on conversation open (
reconnectToInFlightAgentRuns), the pre-turn baseline excludes any output artifacts attached to the in-progress message itself so a newly produced artifact still auto-opens when the reconnected turn finishes.
First-Class Inline Workflow Execution Card & Durable Task-Graph Continuation
Section titled “First-Class Inline Workflow Execution Card & Durable Task-Graph Continuation”When an agent (such as Sage) dispatches a multi-step durable task graph associated with a ConversationDetail message:
- Inline Workflow Execution Card (
.msg-workflow-cardinMessageItemComponent): Renders directly inside.message-body(without requiring the user to open the developer gear menu). Deduplicates parent workflow container tasks (ParentID == null) from ordered step tasks (WorkflowSteps), displays an active shimmer progress bar, completed step ratio (X/Y steps), live elapsed duration, per-step specialist agent badges (@Query Builder,@Research Report Writer), and a one-click slide-over step inspector drawer (mj-task-detail-panel). - Real-Time Multi-Tab Broadcast & Continuation (
ConversationChatAreaComponent):TaskGraphFrameBroadcasterandTaskGraphContinuationDelivererpublish task-node progress and completion frames overPUSH_STATUS_UPDATES_TOPIC(sessionId: '*', filtered byownerUserIdinPushStatusResolver). When a durable workflow finishes withcontinuation: 'reinvoke', the server creates a new visible AIConversationDetailreply row, runs the synthesizing agent, persists both step-level and continuation artifacts (ProcessAgentArtifacts), and notifiesConversationChatAreaComponent(NewMessageCreated: true) so the active conversation reloads messages, refreshes task statuses, and auto-opens any newly produced artifact.
AI Agent Workflows & Tasks Command Center (TasksFullViewComponent)
Section titled “AI Agent Workflows & Tasks Command Center (TasksFullViewComponent)”TasksFullViewComponent (<mj-tasks-full-view>) provides the AI Agent Workflows & Tasks Command Center for inspecting and navigating agent-invoked workflows and standalone tasks across conversations:
- 5-Card KPI Metric Ribbon: Interactive filter cards for Active Now, Completed, Needs Attention (Failed/Cancelled), Total Workflows, and Avg Execution Time (plus completed-step throughput).
- Unified Filter & Search Bar: Instant full-text search across workflow names, descriptions, and conversation names, status pill filters (
All,Active,Completed,Failed), a dynamic Specialist Agent dropdown filter, and a scope toggle (All Tasks vs. My Tasks). - 3 Synchronized View Modes:
- Workflow Cards View: Hierarchy-aware cards showing workflow status, parent conversation deep-link pill, horizontal Agent Pipeline chips (
Step 1 -> Step 2), progress bar, and expandable step rows with step numbers, agent badges, durations, and output previews. - Execution Timeline & DAG View: Custom second/minute-resolution Execution Waterfall bar chart for multi-step workflows (rendered automatically whenever tasks span
< 2 dayswhere day-level Gantt bars would collapse), paired with the fullmj-gantt-task-viewerfor multi-day schedules. - Slide-Over Workflow & Step Inspector Drawer: Selecting any workflow or step opens a contextual right-hand inspector drawer with step-switcher tabs, clickable telemetry pills for the AI Agent (
MJ: AI Agents) and AI Agent Run (MJ: AI Agent Runs), separated Task Instructions (Input) vs. Agent Output / Deliverable (parsed cleanly around the--- Output ---delimiter), and a primary Jump to Chat action.
- Workflow Cards View: Hierarchy-aware cards showing workflow status, parent conversation deep-link pill, horizontal Agent Pipeline chips (
MJ Entity Integration
Section titled “MJ Entity Integration”All data operations use the MemberJunction entity system:
Metadata.GetEntityObject()for entity creationRunViewfor efficient data loading- Proper generic typing throughout
Reactive State Management
Section titled “Reactive State Management”RxJS BehaviorSubject instances for all state, with derived observables using combineLatest and shareReplay(1) for efficient caching.
Dependencies
Section titled “Dependencies”MemberJunction Packages
Section titled “MemberJunction Packages”| Package | Description |
|---|---|
@memberjunction/core | Core framework |
@memberjunction/core-entities | Entity type definitions |
@memberjunction/global | Global utilities |
@memberjunction/graphql-dataprovider | GraphQL data access |
@memberjunction/ng-artifacts | Artifact viewer components |
@memberjunction/ng-code-editor | Code editor component |
@memberjunction/ng-container-directives | Container directives |
@memberjunction/ng-markdown | Markdown rendering |
@memberjunction/ng-shared-generic | Shared generic components |
@memberjunction/ng-testing | Testing framework components |
@memberjunction/ng-whiteboard | The generic collaborative whiteboard (consumed by the realtime Whiteboard channel + artifact viewer) |
@memberjunction/ai-realtime-client | Browser-side realtime drivers (OpenAI / Gemini / ElevenLabs / AssemblyAI) used by RealtimeSessionService |
Kendo UI Packages
Section titled “Kendo UI Packages”Uses @progress/kendo-angular-dialog, @progress/kendo-angular-buttons, @progress/kendo-angular-inputs, @progress/kendo-angular-layout, @progress/kendo-angular-indicators, @progress/kendo-angular-dropdowns, @progress/kendo-angular-notification, @progress/kendo-angular-upload, @progress/kendo-angular-dateinputs.
Peer Dependencies
Section titled “Peer Dependencies”@angular/common^21.x@angular/core^21.x@angular/forms^21.x@angular/router^21.x
cd packages/Angular/Generic/conversationsnpm run buildLicense
Section titled “License”Business Source License 1.1 — see LICENSE for details.