@memberjunction/conversations-runtime 6.1.2 → 6.2.0-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConversationsRuntime.d.ts +10 -0
- package/dist/ConversationsRuntime.d.ts.map +1 -1
- package/dist/ConversationsRuntime.js +12 -0
- package/dist/ConversationsRuntime.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mentions/MentionAutocomplete.d.ts +151 -0
- package/dist/mentions/MentionAutocomplete.d.ts.map +1 -0
- package/dist/mentions/MentionAutocomplete.js +468 -0
- package/dist/mentions/MentionAutocomplete.js.map +1 -0
- package/dist/mentions/MentionSuggestion.d.ts +66 -0
- package/dist/mentions/MentionSuggestion.d.ts.map +1 -0
- package/dist/mentions/MentionSuggestion.js +19 -0
- package/dist/mentions/MentionSuggestion.js.map +1 -0
- package/dist/mentions/SkillNarrowing.d.ts +17 -0
- package/dist/mentions/SkillNarrowing.d.ts.map +1 -0
- package/dist/mentions/SkillNarrowing.js +17 -0
- package/dist/mentions/SkillNarrowing.js.map +1 -0
- package/dist/timeline/RealtimeSessionTimeline.d.ts +182 -0
- package/dist/timeline/RealtimeSessionTimeline.d.ts.map +1 -0
- package/dist/timeline/RealtimeSessionTimeline.js +233 -0
- package/dist/timeline/RealtimeSessionTimeline.js.map +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime-session timeline grouping for the standard conversation message list.
|
|
3
|
+
*
|
|
4
|
+
* `MJ: Conversation Details` rows persisted DURING a live realtime session are stamped
|
|
5
|
+
* with `AgentSessionID` (the transcript relay stamps every caption turn; the artifact
|
|
6
|
+
* junction-anchor rows are stamped too). Rendering those rows as the normal back/forth
|
|
7
|
+
* chat bubbles is wrong — a 40-turn voice call would flood the timeline. Instead the
|
|
8
|
+
* message list collapses each session's stamped rows into ONE distinct timeline element
|
|
9
|
+
* (a session card) at the session's chronological position, from which the user can
|
|
10
|
+
* reopen the session review.
|
|
11
|
+
*
|
|
12
|
+
* This module is PURE (no Angular, no entities) so the grouping pass is unit-testable:
|
|
13
|
+
* the source shape is a minimal structural interface that `MJConversationDetailEntity`
|
|
14
|
+
* satisfies via its getters.
|
|
15
|
+
*
|
|
16
|
+
* It LIVED in `ng-conversations` and was promoted here unchanged, because purity was
|
|
17
|
+
* never the same thing as being in the right package: the mobile thread rendered every
|
|
18
|
+
* stamped row as an ordinary bubble — precisely what the paragraph above calls wrong —
|
|
19
|
+
* while the only implementation of the rule sat behind an Angular import. Any surface
|
|
20
|
+
* that renders a conversation needs this, so it belongs with the engine they all share.
|
|
21
|
+
* `ng-conversations` re-exports it, so its own consumers are unaffected.
|
|
22
|
+
*/
|
|
23
|
+
/** The minimal detail-row shape the grouping pass reads (satisfied by `MJConversationDetailEntity`). */
|
|
24
|
+
export interface RealtimeTimelineSourceDetail {
|
|
25
|
+
ID: string;
|
|
26
|
+
/** Stamped session id for rows persisted during a live realtime session; null for normal chat rows. */
|
|
27
|
+
AgentSessionID: string | null;
|
|
28
|
+
/** `User` | `AI` | `Error` on the entity; widened to string for structural compatibility. */
|
|
29
|
+
Role: string;
|
|
30
|
+
Message: string | null;
|
|
31
|
+
/** Hidden rows (system/junction-anchor details) never count as visible turns. */
|
|
32
|
+
HiddenToUser: boolean;
|
|
33
|
+
__mj_CreatedAt: Date | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One realtime session's collapsed block: everything the timeline card renders that can
|
|
37
|
+
* be computed from the conversation's already-loaded detail rows (no extra queries).
|
|
38
|
+
*/
|
|
39
|
+
export interface RealtimeSessionTimelineGroup {
|
|
40
|
+
/** `MJ: AI Agent Sessions.ID` the block's rows are stamped with (original casing of the first row). */
|
|
41
|
+
SessionID: string;
|
|
42
|
+
/** Timestamp of the session's first stamped row (≈ when the session's transcript began). */
|
|
43
|
+
StartedAt: Date | null;
|
|
44
|
+
/** Timestamp of the session's last stamped row (≈ when the transcript ended). */
|
|
45
|
+
EndedAt: Date | null;
|
|
46
|
+
/** VISIBLE caption turns: non-hidden `User`/`AI` rows with text (mirrors review-mode's turn mapping). */
|
|
47
|
+
TurnCount: number;
|
|
48
|
+
/** ALL stamped rows folded into this block, hidden anchors included. */
|
|
49
|
+
DetailCount: number;
|
|
50
|
+
/** Who spoke the last visible turn (AI → `Assistant`), or null when no visible turn exists. */
|
|
51
|
+
LastTurnRole: 'User' | 'Assistant' | null;
|
|
52
|
+
/** The last visible turn's text (untruncated — the card ellipsizes), or null. */
|
|
53
|
+
LastTurnPreview: string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Session-row metadata the card enriches itself with when the host has it (one batched
|
|
57
|
+
* `MJ: AI Agent Sessions` lookup per conversation). All-optional by design: a missing
|
|
58
|
+
* meta entry degrades the card to its generic label with no status chip.
|
|
59
|
+
*/
|
|
60
|
+
export interface RealtimeSessionTimelineMeta {
|
|
61
|
+
/** `MJ: AI Agent Sessions.ID`. */
|
|
62
|
+
SessionID: string;
|
|
63
|
+
/** Denormalized agent display name from the session view (usually the co-agent). */
|
|
64
|
+
AgentName: string | null;
|
|
65
|
+
Status: 'Active' | 'Closed' | 'Idle' | null;
|
|
66
|
+
/** Why the session closed (`Error` | `Explicit` | `Janitor` | `Shutdown`), when closed. */
|
|
67
|
+
CloseReason: string | null;
|
|
68
|
+
ClosedAt: Date | null;
|
|
69
|
+
}
|
|
70
|
+
/** One renderable timeline entry: a normal chat message OR a collapsed session block. */
|
|
71
|
+
export type ConversationTimelineItem<T extends RealtimeTimelineSourceDetail> = {
|
|
72
|
+
Kind: 'message';
|
|
73
|
+
Detail: T;
|
|
74
|
+
} | {
|
|
75
|
+
Kind: 'session';
|
|
76
|
+
Group: RealtimeSessionTimelineGroup;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Builds the renderable conversation timeline from the loaded detail rows (chronological,
|
|
80
|
+
* as the engine returns them):
|
|
81
|
+
* - rows WITHOUT an `AgentSessionID` pass through unchanged, in order;
|
|
82
|
+
* - rows WITH one collapse into ONE session block per session id, positioned where the
|
|
83
|
+
* session's FIRST stamped row sits. Later stamped rows of the same session (even when
|
|
84
|
+
* interleaved with normal messages) fold into that same block — one element per
|
|
85
|
+
* session, period.
|
|
86
|
+
*
|
|
87
|
+
* Session-id comparison is case-insensitive ({@link NormalizeUUID}) so SQL Server
|
|
88
|
+
* (uppercase) and PostgreSQL (lowercase) ids group identically.
|
|
89
|
+
*/
|
|
90
|
+
export declare function BuildConversationTimeline<T extends RealtimeTimelineSourceDetail>(details: readonly T[]): ConversationTimelineItem<T>[];
|
|
91
|
+
/**
|
|
92
|
+
* Whether a session-stamped row is a turn a person should see.
|
|
93
|
+
*
|
|
94
|
+
* Exported because a surface that expands a session card to show its transcript must select the
|
|
95
|
+
* same rows the card counted — a list whose length disagrees with the "12 turns" above it reads as
|
|
96
|
+
* a bug even when both numbers are defensible. Hidden rows are the system/junction anchors the
|
|
97
|
+
* relay also stamps.
|
|
98
|
+
*
|
|
99
|
+
* @param detail One stamped detail row.
|
|
100
|
+
*/
|
|
101
|
+
export declare function IsVisibleRealtimeTurn(detail: RealtimeTimelineSourceDetail): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* How a session card's status chip should read.
|
|
104
|
+
*
|
|
105
|
+
* `Tone` is semantic rather than a colour so each surface maps it to its own palette — the web to
|
|
106
|
+
* its chip modifier classes, React Native to its token set.
|
|
107
|
+
*/
|
|
108
|
+
export interface RealtimeSessionStatusChip {
|
|
109
|
+
/** The chip text ("Live", "Ended", "Timed out", …). */
|
|
110
|
+
Label: string;
|
|
111
|
+
/** What the chip means, for styling. */
|
|
112
|
+
Tone: 'live' | 'idle' | 'error' | 'neutral';
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The card's title: the agent's name when the session lookup supplied one, else the generic label.
|
|
116
|
+
*
|
|
117
|
+
* @param meta The session-row enrichment, or null when the lookup was unavailable.
|
|
118
|
+
*/
|
|
119
|
+
export declare function SessionCardTitle(meta: RealtimeSessionTimelineMeta | null | undefined): string;
|
|
120
|
+
/**
|
|
121
|
+
* The card's status chip, or `null` to hide it entirely.
|
|
122
|
+
*
|
|
123
|
+
* Closed sessions show a humanized close reason, falling back to "Closed" for legacy rows that
|
|
124
|
+
* predate the column. No meta at all means no chip — a session whose row could not be read should
|
|
125
|
+
* say nothing rather than guess.
|
|
126
|
+
*
|
|
127
|
+
* @param meta The session-row enrichment, or null when the lookup was unavailable.
|
|
128
|
+
*/
|
|
129
|
+
export declare function SessionCardStatusChip(meta: RealtimeSessionTimelineMeta | null | undefined): RealtimeSessionStatusChip | null;
|
|
130
|
+
/**
|
|
131
|
+
* Whether the block's start and end fall on the same calendar day.
|
|
132
|
+
*
|
|
133
|
+
* Drives the end-time format: a same-day range shows only the time on its right half, so the date
|
|
134
|
+
* is not repeated. An incomplete range counts as same-day, since there is no second date to show.
|
|
135
|
+
*
|
|
136
|
+
* @param group The collapsed session block.
|
|
137
|
+
*/
|
|
138
|
+
export declare function SessionCardIsSameDayRange(group: RealtimeSessionTimelineGroup | null | undefined): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* The `MJ: AI Agent Sessions` row shape the meta lookup reads.
|
|
141
|
+
*
|
|
142
|
+
* Deliberately structural and string-tolerant on the dates: a `simple` RunView returns ISO strings
|
|
143
|
+
* where an entity object would return `Date`, and both callers use `simple`.
|
|
144
|
+
*/
|
|
145
|
+
export interface RealtimeSessionMetaRow {
|
|
146
|
+
ID: string;
|
|
147
|
+
/** Denormalized agent display name from the session view. */
|
|
148
|
+
Agent?: string | null;
|
|
149
|
+
Status?: 'Active' | 'Closed' | 'Idle' | null;
|
|
150
|
+
CloseReason?: string | null;
|
|
151
|
+
ClosedAt?: Date | string | null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The columns the meta lookup needs, so both surfaces ask for the same ones.
|
|
155
|
+
*
|
|
156
|
+
* A host that trims this list gets cards missing their chips on that host only — the kind of
|
|
157
|
+
* divergence that is invisible until someone compares two screenshots.
|
|
158
|
+
*/
|
|
159
|
+
export declare const REALTIME_SESSION_META_FIELDS: readonly string[];
|
|
160
|
+
/**
|
|
161
|
+
* The DISTINCT session ids stamped across `details`, in first-seen order.
|
|
162
|
+
*
|
|
163
|
+
* Deduped case-insensitively ({@link NormalizeUUID}) because SQL Server returns uppercase ids and
|
|
164
|
+
* PostgreSQL lowercase; the original casing of the first occurrence is what comes back, since that
|
|
165
|
+
* is what the `ID IN (…)` filter has to match.
|
|
166
|
+
*
|
|
167
|
+
* @param details The conversation's loaded detail rows.
|
|
168
|
+
*/
|
|
169
|
+
export declare function CollectRealtimeSessionIDs(details: readonly RealtimeTimelineSourceDetail[]): string[];
|
|
170
|
+
/**
|
|
171
|
+
* Maps session rows to the card's meta, keyed by {@link NormalizeUUID} so lookups match however
|
|
172
|
+
* the database cased the id.
|
|
173
|
+
*
|
|
174
|
+
* Tolerant: an unparseable `ClosedAt` becomes `null` rather than an Invalid Date, which would
|
|
175
|
+
* otherwise render as "Invalid Date" on the card.
|
|
176
|
+
*
|
|
177
|
+
* @param rows Rows from `MJ: AI Agent Sessions` (see {@link REALTIME_SESSION_META_FIELDS}).
|
|
178
|
+
*/
|
|
179
|
+
export declare function MapRealtimeSessionMeta(rows: readonly RealtimeSessionMetaRow[] | null | undefined): Map<string, RealtimeSessionTimelineMeta>;
|
|
180
|
+
/** Looks up a session block's meta, matching however the database cased the id. */
|
|
181
|
+
export declare function FindRealtimeSessionMeta(metaMap: ReadonlyMap<string, RealtimeSessionTimelineMeta> | null | undefined, sessionId: string): RealtimeSessionTimelineMeta | null;
|
|
182
|
+
//# sourceMappingURL=RealtimeSessionTimeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RealtimeSessionTimeline.d.ts","sourceRoot":"","sources":["../../src/timeline/RealtimeSessionTimeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime-session timeline grouping for the standard conversation message list.
|
|
3
|
+
*
|
|
4
|
+
* `MJ: Conversation Details` rows persisted DURING a live realtime session are stamped
|
|
5
|
+
* with `AgentSessionID` (the transcript relay stamps every caption turn; the artifact
|
|
6
|
+
* junction-anchor rows are stamped too). Rendering those rows as the normal back/forth
|
|
7
|
+
* chat bubbles is wrong — a 40-turn voice call would flood the timeline. Instead the
|
|
8
|
+
* message list collapses each session's stamped rows into ONE distinct timeline element
|
|
9
|
+
* (a session card) at the session's chronological position, from which the user can
|
|
10
|
+
* reopen the session review.
|
|
11
|
+
*
|
|
12
|
+
* This module is PURE (no Angular, no entities) so the grouping pass is unit-testable:
|
|
13
|
+
* the source shape is a minimal structural interface that `MJConversationDetailEntity`
|
|
14
|
+
* satisfies via its getters.
|
|
15
|
+
*
|
|
16
|
+
* It LIVED in `ng-conversations` and was promoted here unchanged, because purity was
|
|
17
|
+
* never the same thing as being in the right package: the mobile thread rendered every
|
|
18
|
+
* stamped row as an ordinary bubble — precisely what the paragraph above calls wrong —
|
|
19
|
+
* while the only implementation of the rule sat behind an Angular import. Any surface
|
|
20
|
+
* that renders a conversation needs this, so it belongs with the engine they all share.
|
|
21
|
+
* `ng-conversations` re-exports it, so its own consumers are unaffected.
|
|
22
|
+
*/
|
|
23
|
+
import { NormalizeUUID } from '@memberjunction/global';
|
|
24
|
+
/**
|
|
25
|
+
* Builds the renderable conversation timeline from the loaded detail rows (chronological,
|
|
26
|
+
* as the engine returns them):
|
|
27
|
+
* - rows WITHOUT an `AgentSessionID` pass through unchanged, in order;
|
|
28
|
+
* - rows WITH one collapse into ONE session block per session id, positioned where the
|
|
29
|
+
* session's FIRST stamped row sits. Later stamped rows of the same session (even when
|
|
30
|
+
* interleaved with normal messages) fold into that same block — one element per
|
|
31
|
+
* session, period.
|
|
32
|
+
*
|
|
33
|
+
* Session-id comparison is case-insensitive ({@link NormalizeUUID}) so SQL Server
|
|
34
|
+
* (uppercase) and PostgreSQL (lowercase) ids group identically.
|
|
35
|
+
*/
|
|
36
|
+
export function BuildConversationTimeline(details) {
|
|
37
|
+
const items = [];
|
|
38
|
+
const groupsBySessionKey = new Map();
|
|
39
|
+
for (const detail of details) {
|
|
40
|
+
const sessionId = normalizeSessionId(detail.AgentSessionID);
|
|
41
|
+
if (!sessionId) {
|
|
42
|
+
items.push({ Kind: 'message', Detail: detail });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const key = NormalizeUUID(sessionId);
|
|
46
|
+
let group = groupsBySessionKey.get(key);
|
|
47
|
+
if (!group) {
|
|
48
|
+
group = createGroup(sessionId);
|
|
49
|
+
groupsBySessionKey.set(key, group);
|
|
50
|
+
items.push({ Kind: 'session', Group: group });
|
|
51
|
+
}
|
|
52
|
+
foldDetailIntoGroup(group, detail);
|
|
53
|
+
}
|
|
54
|
+
return items;
|
|
55
|
+
}
|
|
56
|
+
/** Trims the stamped session id; empty/whitespace ids are treated as unstamped. */
|
|
57
|
+
function normalizeSessionId(raw) {
|
|
58
|
+
const trimmed = raw?.trim() ?? '';
|
|
59
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
60
|
+
}
|
|
61
|
+
/** A fresh, empty session block for the given session id. */
|
|
62
|
+
function createGroup(sessionId) {
|
|
63
|
+
return {
|
|
64
|
+
SessionID: sessionId,
|
|
65
|
+
StartedAt: null,
|
|
66
|
+
EndedAt: null,
|
|
67
|
+
TurnCount: 0,
|
|
68
|
+
DetailCount: 0,
|
|
69
|
+
LastTurnRole: null,
|
|
70
|
+
LastTurnPreview: null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Folds one stamped detail row into its session block (range, counts, last-turn preview). */
|
|
74
|
+
function foldDetailIntoGroup(group, detail) {
|
|
75
|
+
group.DetailCount++;
|
|
76
|
+
const at = toDate(detail.__mj_CreatedAt);
|
|
77
|
+
if (at) {
|
|
78
|
+
if (!group.StartedAt || at.getTime() < group.StartedAt.getTime()) {
|
|
79
|
+
group.StartedAt = at;
|
|
80
|
+
}
|
|
81
|
+
if (!group.EndedAt || at.getTime() > group.EndedAt.getTime()) {
|
|
82
|
+
group.EndedAt = at;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (IsVisibleRealtimeTurn(detail)) {
|
|
86
|
+
group.TurnCount++;
|
|
87
|
+
group.LastTurnRole = detail.Role === 'AI' ? 'Assistant' : 'User';
|
|
88
|
+
group.LastTurnPreview = detail.Message?.trim() ?? '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a session-stamped row is a turn a person should see.
|
|
93
|
+
*
|
|
94
|
+
* Exported because a surface that expands a session card to show its transcript must select the
|
|
95
|
+
* same rows the card counted — a list whose length disagrees with the "12 turns" above it reads as
|
|
96
|
+
* a bug even when both numbers are defensible. Hidden rows are the system/junction anchors the
|
|
97
|
+
* relay also stamps.
|
|
98
|
+
*
|
|
99
|
+
* @param detail One stamped detail row.
|
|
100
|
+
*/
|
|
101
|
+
export function IsVisibleRealtimeTurn(detail) {
|
|
102
|
+
const text = detail.Message?.trim() ?? '';
|
|
103
|
+
return !detail.HiddenToUser && text.length > 0 && (detail.Role === 'User' || detail.Role === 'AI');
|
|
104
|
+
}
|
|
105
|
+
/** Tolerant date conversion — raw cache rows can carry ISO strings instead of Dates. */
|
|
106
|
+
function toDate(value) {
|
|
107
|
+
if (value == null) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
111
|
+
return isNaN(date.getTime()) ? null : date;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The card's title: the agent's name when the session lookup supplied one, else the generic label.
|
|
115
|
+
*
|
|
116
|
+
* @param meta The session-row enrichment, or null when the lookup was unavailable.
|
|
117
|
+
*/
|
|
118
|
+
export function SessionCardTitle(meta) {
|
|
119
|
+
const agent = meta?.AgentName?.trim();
|
|
120
|
+
return agent ? `Realtime session · ${agent}` : 'Realtime session';
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The card's status chip, or `null` to hide it entirely.
|
|
124
|
+
*
|
|
125
|
+
* Closed sessions show a humanized close reason, falling back to "Closed" for legacy rows that
|
|
126
|
+
* predate the column. No meta at all means no chip — a session whose row could not be read should
|
|
127
|
+
* say nothing rather than guess.
|
|
128
|
+
*
|
|
129
|
+
* @param meta The session-row enrichment, or null when the lookup was unavailable.
|
|
130
|
+
*/
|
|
131
|
+
export function SessionCardStatusChip(meta) {
|
|
132
|
+
switch (meta?.Status) {
|
|
133
|
+
case 'Closed':
|
|
134
|
+
return closeReasonChip(meta.CloseReason);
|
|
135
|
+
case 'Active':
|
|
136
|
+
return { Label: 'Live', Tone: 'live' };
|
|
137
|
+
case 'Idle':
|
|
138
|
+
return { Label: 'Idle', Tone: 'idle' };
|
|
139
|
+
default:
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether the block's start and end fall on the same calendar day.
|
|
145
|
+
*
|
|
146
|
+
* Drives the end-time format: a same-day range shows only the time on its right half, so the date
|
|
147
|
+
* is not repeated. An incomplete range counts as same-day, since there is no second date to show.
|
|
148
|
+
*
|
|
149
|
+
* @param group The collapsed session block.
|
|
150
|
+
*/
|
|
151
|
+
export function SessionCardIsSameDayRange(group) {
|
|
152
|
+
const start = group?.StartedAt;
|
|
153
|
+
const end = group?.EndedAt;
|
|
154
|
+
if (!start || !end) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return start.toDateString() === end.toDateString();
|
|
158
|
+
}
|
|
159
|
+
/** Maps a close reason to its chip. `Error` is the only one that reads as a failure. */
|
|
160
|
+
function closeReasonChip(reason) {
|
|
161
|
+
switch (reason) {
|
|
162
|
+
case 'Explicit':
|
|
163
|
+
return { Label: 'Ended', Tone: 'neutral' };
|
|
164
|
+
case 'Error':
|
|
165
|
+
return { Label: 'Error', Tone: 'error' };
|
|
166
|
+
case 'Janitor':
|
|
167
|
+
return { Label: 'Timed out', Tone: 'neutral' };
|
|
168
|
+
case 'Shutdown':
|
|
169
|
+
return { Label: 'Server shutdown', Tone: 'neutral' };
|
|
170
|
+
default:
|
|
171
|
+
return { Label: 'Closed', Tone: 'neutral' };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The columns the meta lookup needs, so both surfaces ask for the same ones.
|
|
176
|
+
*
|
|
177
|
+
* A host that trims this list gets cards missing their chips on that host only — the kind of
|
|
178
|
+
* divergence that is invisible until someone compares two screenshots.
|
|
179
|
+
*/
|
|
180
|
+
export const REALTIME_SESSION_META_FIELDS = ['ID', 'Agent', 'Status', 'CloseReason', 'ClosedAt'];
|
|
181
|
+
/**
|
|
182
|
+
* The DISTINCT session ids stamped across `details`, in first-seen order.
|
|
183
|
+
*
|
|
184
|
+
* Deduped case-insensitively ({@link NormalizeUUID}) because SQL Server returns uppercase ids and
|
|
185
|
+
* PostgreSQL lowercase; the original casing of the first occurrence is what comes back, since that
|
|
186
|
+
* is what the `ID IN (…)` filter has to match.
|
|
187
|
+
*
|
|
188
|
+
* @param details The conversation's loaded detail rows.
|
|
189
|
+
*/
|
|
190
|
+
export function CollectRealtimeSessionIDs(details) {
|
|
191
|
+
const ids = [];
|
|
192
|
+
const seen = new Set();
|
|
193
|
+
for (const detail of details) {
|
|
194
|
+
const raw = detail.AgentSessionID?.trim() ?? '';
|
|
195
|
+
if (raw.length === 0) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const key = NormalizeUUID(raw);
|
|
199
|
+
if (!seen.has(key)) {
|
|
200
|
+
seen.add(key);
|
|
201
|
+
ids.push(raw);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return ids;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Maps session rows to the card's meta, keyed by {@link NormalizeUUID} so lookups match however
|
|
208
|
+
* the database cased the id.
|
|
209
|
+
*
|
|
210
|
+
* Tolerant: an unparseable `ClosedAt` becomes `null` rather than an Invalid Date, which would
|
|
211
|
+
* otherwise render as "Invalid Date" on the card.
|
|
212
|
+
*
|
|
213
|
+
* @param rows Rows from `MJ: AI Agent Sessions` (see {@link REALTIME_SESSION_META_FIELDS}).
|
|
214
|
+
*/
|
|
215
|
+
export function MapRealtimeSessionMeta(rows) {
|
|
216
|
+
const map = new Map();
|
|
217
|
+
for (const row of rows ?? []) {
|
|
218
|
+
const closedAt = toDate(row.ClosedAt);
|
|
219
|
+
map.set(NormalizeUUID(row.ID), {
|
|
220
|
+
SessionID: row.ID,
|
|
221
|
+
AgentName: row.Agent ?? null,
|
|
222
|
+
Status: row.Status ?? null,
|
|
223
|
+
CloseReason: row.CloseReason ?? null,
|
|
224
|
+
ClosedAt: closedAt
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return map;
|
|
228
|
+
}
|
|
229
|
+
/** Looks up a session block's meta, matching however the database cased the id. */
|
|
230
|
+
export function FindRealtimeSessionMeta(metaMap, sessionId) {
|
|
231
|
+
return metaMap?.get(NormalizeUUID(sessionId)) ?? null;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=RealtimeSessionTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RealtimeSessionTimeline.js","sourceRoot":"","sources":["../../src/timeline/RealtimeSessionTimeline.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/conversations-runtime",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.2.0-edge.0",
|
|
5
5
|
"description": "MemberJunction: Framework-agnostic Conversations Runtime — orchestration, mention parsing, overlay/workspace bridge, default-agent resolution, client-tool registry, sessions observability. Pure TypeScript, zero UX dependencies. Consumable from browser (Angular widget, React, Vue, etc.) or server (Node workers, CLI tools, test harnesses).",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"author": "MemberJunction.com",
|
|
12
12
|
"license": "BUSL-1.1",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@memberjunction/ai-agent-client": "6.
|
|
15
|
-
"@memberjunction/ai-core-plus": "6.
|
|
16
|
-
"@memberjunction/ai-engine-base": "6.
|
|
17
|
-
"@memberjunction/core": "6.
|
|
18
|
-
"@memberjunction/core-entities": "6.
|
|
19
|
-
"@memberjunction/global": "6.
|
|
20
|
-
"@memberjunction/graphql-dataprovider": "6.
|
|
14
|
+
"@memberjunction/ai-agent-client": "6.2.0-edge.0",
|
|
15
|
+
"@memberjunction/ai-core-plus": "6.2.0-edge.0",
|
|
16
|
+
"@memberjunction/ai-engine-base": "6.2.0-edge.0",
|
|
17
|
+
"@memberjunction/core": "6.2.0-edge.0",
|
|
18
|
+
"@memberjunction/core-entities": "6.2.0-edge.0",
|
|
19
|
+
"@memberjunction/global": "6.2.0-edge.0",
|
|
20
|
+
"@memberjunction/graphql-dataprovider": "6.2.0-edge.0",
|
|
21
21
|
"rxjs": "^7.8.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|