@flamingo-stack/openframe-frontend-core 0.0.589 → 0.0.590-2020.5606.1
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/{chunk-EGUU3QCU.cjs → chunk-2564HPMD.cjs} +3 -3
- package/dist/{chunk-EGUU3QCU.cjs.map → chunk-2564HPMD.cjs.map} +1 -1
- package/dist/{chunk-JDINUWND.cjs → chunk-2MY6BT6P.cjs} +69 -69
- package/dist/{chunk-JDINUWND.cjs.map → chunk-2MY6BT6P.cjs.map} +1 -1
- package/dist/{chunk-G45RGWHF.cjs → chunk-353GRCTE.cjs} +37 -10
- package/dist/chunk-353GRCTE.cjs.map +1 -0
- package/dist/{chunk-5CHW7AMP.cjs → chunk-3BQESV5G.cjs} +10 -10
- package/dist/{chunk-5CHW7AMP.cjs.map → chunk-3BQESV5G.cjs.map} +1 -1
- package/dist/{chunk-KK3XU6YJ.cjs → chunk-3WZQ5RXS.cjs} +4 -4
- package/dist/{chunk-KK3XU6YJ.cjs.map → chunk-3WZQ5RXS.cjs.map} +1 -1
- package/dist/{chunk-A6X7EXP6.cjs → chunk-5KFIUNKN.cjs} +10 -10
- package/dist/{chunk-A6X7EXP6.cjs.map → chunk-5KFIUNKN.cjs.map} +1 -1
- package/dist/{chunk-CJOWLRUU.js → chunk-6BPIDRBI.js} +2 -2
- package/dist/{chunk-FVRRYJ6P.cjs → chunk-7H4RUNIL.cjs} +22 -22
- package/dist/{chunk-FVRRYJ6P.cjs.map → chunk-7H4RUNIL.cjs.map} +1 -1
- package/dist/{chunk-CN36E4PK.js → chunk-7JPKCUF7.js} +2 -2
- package/dist/{chunk-IYDHV2CS.cjs → chunk-7O7CAZYG.cjs} +43 -43
- package/dist/{chunk-IYDHV2CS.cjs.map → chunk-7O7CAZYG.cjs.map} +1 -1
- package/dist/{chunk-LGBNHWDN.js → chunk-ACUXSU6J.js} +3 -3
- package/dist/{chunk-KVNGYFFP.js → chunk-AV4UXMOH.js} +3 -3
- package/dist/{chunk-4ZKAMPBX.js → chunk-BKOCXNAP.js} +2 -2
- package/dist/{chunk-MENQLD57.cjs → chunk-CQHFRJSG.cjs} +5 -5
- package/dist/{chunk-MENQLD57.cjs.map → chunk-CQHFRJSG.cjs.map} +1 -1
- package/dist/{chunk-3OCYNMZ5.js → chunk-CT6I3HOP.js} +3 -3
- package/dist/{chunk-C6RJRSIM.cjs → chunk-DAUDWRE4.cjs} +3 -3
- package/dist/{chunk-C6RJRSIM.cjs.map → chunk-DAUDWRE4.cjs.map} +1 -1
- package/dist/{chunk-NXZC6UTN.js → chunk-DFUO7J3W.js} +2 -2
- package/dist/{chunk-TOYHZHTK.js → chunk-E5NSDLYJ.js} +2 -2
- package/dist/{chunk-77Y7R243.js → chunk-GAXC43J4.js} +5 -5
- package/dist/{chunk-EGYX55HT.cjs → chunk-I3HBFKKM.cjs} +159 -159
- package/dist/{chunk-EGYX55HT.cjs.map → chunk-I3HBFKKM.cjs.map} +1 -1
- package/dist/{chunk-ADANP3ZA.cjs → chunk-JIIA5BIJ.cjs} +33 -33
- package/dist/{chunk-ADANP3ZA.cjs.map → chunk-JIIA5BIJ.cjs.map} +1 -1
- package/dist/{chunk-PAUJQV3C.cjs → chunk-KIMRNY7A.cjs} +15 -15
- package/dist/{chunk-PAUJQV3C.cjs.map → chunk-KIMRNY7A.cjs.map} +1 -1
- package/dist/{chunk-FQBR6SCA.js → chunk-KLNHD4TC.js} +3 -3
- package/dist/{chunk-CNKVEVUN.js → chunk-LDNRL62P.js} +5 -5
- package/dist/{chunk-I5CJJFEJ.js → chunk-LKMRJ2CP.js} +3 -3
- package/dist/{chunk-KWTV3KGY.cjs → chunk-QEV5HPDQ.cjs} +9 -9
- package/dist/{chunk-KWTV3KGY.cjs.map → chunk-QEV5HPDQ.cjs.map} +1 -1
- package/dist/{chunk-WUCYI3BC.cjs → chunk-RLABO5PY.cjs} +24 -24
- package/dist/{chunk-WUCYI3BC.cjs.map → chunk-RLABO5PY.cjs.map} +1 -1
- package/dist/{chunk-5FNWOF46.js → chunk-RLC5IISG.js} +6 -6
- package/dist/{chunk-GKPY7VLK.js → chunk-RM3PTDSI.js} +4 -4
- package/dist/{chunk-NIDPX52C.js → chunk-T3WAHAMH.js} +5 -5
- package/dist/{chunk-D7YISHXC.js → chunk-V7RB3EUW.js} +37 -10
- package/dist/{chunk-D7YISHXC.js.map → chunk-V7RB3EUW.js.map} +1 -1
- package/dist/{chunk-BONXSRHZ.cjs → chunk-WIAEFA2K.cjs} +21 -21
- package/dist/{chunk-BONXSRHZ.cjs.map → chunk-WIAEFA2K.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +36 -36
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/package.json +1 -1
- package/src/components/chat/__tests__/ticket-event.test.ts +107 -0
- package/src/components/chat/stream/message-mutations.ts +47 -4
- package/src/components/chat/utils/process-historical-messages.ts +18 -6
- package/dist/chunk-G45RGWHF.cjs.map +0 -1
- /package/dist/{chunk-CJOWLRUU.js.map → chunk-6BPIDRBI.js.map} +0 -0
- /package/dist/{chunk-CN36E4PK.js.map → chunk-7JPKCUF7.js.map} +0 -0
- /package/dist/{chunk-LGBNHWDN.js.map → chunk-ACUXSU6J.js.map} +0 -0
- /package/dist/{chunk-KVNGYFFP.js.map → chunk-AV4UXMOH.js.map} +0 -0
- /package/dist/{chunk-4ZKAMPBX.js.map → chunk-BKOCXNAP.js.map} +0 -0
- /package/dist/{chunk-3OCYNMZ5.js.map → chunk-CT6I3HOP.js.map} +0 -0
- /package/dist/{chunk-NXZC6UTN.js.map → chunk-DFUO7J3W.js.map} +0 -0
- /package/dist/{chunk-TOYHZHTK.js.map → chunk-E5NSDLYJ.js.map} +0 -0
- /package/dist/{chunk-77Y7R243.js.map → chunk-GAXC43J4.js.map} +0 -0
- /package/dist/{chunk-FQBR6SCA.js.map → chunk-KLNHD4TC.js.map} +0 -0
- /package/dist/{chunk-CNKVEVUN.js.map → chunk-LDNRL62P.js.map} +0 -0
- /package/dist/{chunk-I5CJJFEJ.js.map → chunk-LKMRJ2CP.js.map} +0 -0
- /package/dist/{chunk-5FNWOF46.js.map → chunk-RLC5IISG.js.map} +0 -0
- /package/dist/{chunk-GKPY7VLK.js.map → chunk-RM3PTDSI.js.map} +0 -0
- /package/dist/{chunk-NIDPX52C.js.map → chunk-T3WAHAMH.js.map} +0 -0
|
@@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
18
18
|
import { decodeNatsChunk } from '../../../chat-protocol/nats-decoder';
|
|
19
19
|
import { createChatStreamReducer } from '../stream/chat-stream-reducer';
|
|
20
20
|
import type { HistoricalMessage, MessageSegment, TicketEventSegment } from '../types';
|
|
21
|
+
import type { UnifiedChatMessage } from '../types/unified-chat-state.types';
|
|
21
22
|
import { MessageSegmentAccumulator } from '../utils/message-segment-accumulator';
|
|
22
23
|
import { processHistoricalMessages } from '../utils/process-historical-messages';
|
|
23
24
|
|
|
@@ -312,6 +313,112 @@ describe('processHistoricalMessages — ticket event', () => {
|
|
|
312
313
|
});
|
|
313
314
|
});
|
|
314
315
|
|
|
316
|
+
describe('processHistoricalMessages — row stream sequence', () => {
|
|
317
|
+
it('stamps the hydrated card with the row seq (standalone row)', () => {
|
|
318
|
+
// TICKET_EVENT chunks are standalone, one per row, so the row's
|
|
319
|
+
// `lastChunkStreamSeq` IS the event's sequence. Without the stamp the
|
|
320
|
+
// hydrated card never joins a stale-consumer/catch-up replay of the same
|
|
321
|
+
// chunk in the store, and the replayed copy rendered as a second
|
|
322
|
+
// identical card (the resolve/reopen duplication).
|
|
323
|
+
const { messages } = processHistoricalMessages([
|
|
324
|
+
{
|
|
325
|
+
id: 'm1',
|
|
326
|
+
createdAt: '2026-08-31T00:54:00Z',
|
|
327
|
+
owner: { type: 'ASSISTANT' },
|
|
328
|
+
lastChunkStreamSeq: 412,
|
|
329
|
+
messageData: [{ type: 'TICKET_EVENT', kind: 'RESOLVED', actorType: 'AI' } as never],
|
|
330
|
+
},
|
|
331
|
+
]);
|
|
332
|
+
expect(events(messages[0].content)[0].streamSeq).toBe(412);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('does NOT stamp a bundled row - its seq belongs to the LAST chunk', () => {
|
|
336
|
+
const { messages } = processHistoricalMessages([
|
|
337
|
+
{
|
|
338
|
+
id: 'm1',
|
|
339
|
+
createdAt: '2026-08-31T00:54:00Z',
|
|
340
|
+
owner: { type: 'ASSISTANT' },
|
|
341
|
+
lastChunkStreamSeq: 413,
|
|
342
|
+
messageData: [
|
|
343
|
+
{ type: 'TICKET_EVENT', kind: 'RESOLVED', actorType: 'AI' } as never,
|
|
344
|
+
{ type: 'TEXT', text: 'trailing text' } as never,
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
]);
|
|
348
|
+
expect(events(messages[0].content)[0].streamSeq).toBeUndefined();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('reducer over a hydrated thread — the resolve/reopen duplication', () => {
|
|
353
|
+
// The reported bug (admin PSA chat, ticket 86ak8a74d; same lib hole as the
|
|
354
|
+
// desktop dup in 86ak56ehp): reopening a dialog hydrates the persisted
|
|
355
|
+
// TICKET_EVENT row into the thread, then a consumer created with a stale
|
|
356
|
+
// `optStartSeq` replays the same chunk. The twins meet ONLY in the store
|
|
357
|
+
// (a complete hydrated tail seeds no accumulator), so the join must happen
|
|
358
|
+
// in `appendToTrailingAssistant` - it used to push the replay beside the
|
|
359
|
+
// hydrated card as a second identical block.
|
|
360
|
+
const hydratedThread = (card: TicketEventSegment): UnifiedChatMessage[] => [
|
|
361
|
+
{
|
|
362
|
+
id: 'assistant-m1',
|
|
363
|
+
role: 'assistant',
|
|
364
|
+
content: '',
|
|
365
|
+
segments: [{ type: 'text', text: 'Excellent! Feel free to reach out.' }, card],
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
const replay = (reducer: ReturnType<typeof createChatStreamReducer>, chunk: unknown) => {
|
|
370
|
+
const event = decodeNatsChunk(chunk);
|
|
371
|
+
if (event) reducer.apply(event);
|
|
372
|
+
return reducer.state.messages[reducer.state.messages.length - 1]?.segments ?? [];
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
it('joins the replay with a seq-stamped hydrated card (one card, row time kept)', () => {
|
|
376
|
+
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
377
|
+
reducer.initializeWithState(
|
|
378
|
+
hydratedThread({
|
|
379
|
+
type: 'ticket_event',
|
|
380
|
+
data: { kind: 'RESOLVED', actorType: 'AI' },
|
|
381
|
+
streamSeq: 412,
|
|
382
|
+
occurredAt: new Date('2026-08-31T00:54:00Z'),
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
const segments = replay(reducer, { type: 'TICKET_EVENT', kind: 'RESOLVED', actorType: 'AI', streamSeq: 412 });
|
|
386
|
+
const cards = events(segments);
|
|
387
|
+
expect(cards).toHaveLength(1);
|
|
388
|
+
// First-known time wins: the replay only knows its late arrival time.
|
|
389
|
+
expect(cards[0].occurredAt).toEqual(new Date('2026-08-31T00:54:00Z'));
|
|
390
|
+
// The reply the user is reading stays intact.
|
|
391
|
+
expect(segments.find(s => s.type === 'text')).toMatchObject({ text: 'Excellent! Feel free to reach out.' });
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('joins the replay with a seq-LESS hydrated twin (rows persisted before the seq stamp)', () => {
|
|
395
|
+
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
396
|
+
reducer.initializeWithState(
|
|
397
|
+
hydratedThread({
|
|
398
|
+
type: 'ticket_event',
|
|
399
|
+
data: { kind: 'RESOLVED', actorType: 'AI' },
|
|
400
|
+
occurredAt: new Date('2026-08-31T00:54:00Z'),
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
const cards = events(replay(reducer, { type: 'TICKET_EVENT', kind: 'RESOLVED', actorType: 'AI', streamSeq: 412 }));
|
|
404
|
+
expect(cards).toHaveLength(1);
|
|
405
|
+
expect(cards[0].streamSeq).toBe(412);
|
|
406
|
+
expect(cards[0].occurredAt).toEqual(new Date('2026-08-31T00:54:00Z'));
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('keeps a genuinely repeated cycle distinct across hydrate + live', () => {
|
|
410
|
+
// resolve (hydrated, seq-less legacy row) -> live reopen -> live resolve
|
|
411
|
+
// payload-identical to the first: only the LAST ticket event may
|
|
412
|
+
// payload-match, so the final resolve renders as its own card.
|
|
413
|
+
const reducer = createChatStreamReducer({ transport: 'nats' });
|
|
414
|
+
reducer.initializeWithState(hydratedThread({ type: 'ticket_event', data: { kind: 'RESOLVED', actorType: 'AI' } }));
|
|
415
|
+
replay(reducer, { type: 'TICKET_EVENT', kind: 'REOPENED', actorType: 'CLIENT', streamSeq: 413 });
|
|
416
|
+
const cards = events(replay(reducer, { type: 'TICKET_EVENT', kind: 'RESOLVED', actorType: 'AI', streamSeq: 414 }));
|
|
417
|
+
expect(cards).toHaveLength(3);
|
|
418
|
+
expect(cards.map(c => c.data.kind)).toEqual(['RESOLVED', 'REOPENED', 'RESOLVED']);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
315
422
|
describe('accumulator — occurredAt across the hydrate/catch-up overlap', () => {
|
|
316
423
|
it('keeps the hydrated row time when the seq-stamped redelivery carries none', () => {
|
|
317
424
|
const accumulator = new MessageSegmentAccumulator();
|
|
@@ -95,14 +95,56 @@ function upsertKey(seg: MessageSegment): string | null {
|
|
|
95
95
|
return `escalated:${seg.data.ticketId}`;
|
|
96
96
|
case 'ticket_event':
|
|
97
97
|
// The stream sequence is the event's only stable id; a seq-less segment
|
|
98
|
-
// (hydrated
|
|
99
|
-
//
|
|
98
|
+
// (a row hydrated without `lastChunkStreamSeq`) is pushed raw and joined
|
|
99
|
+
// by `seqlessTicketEventTwinIndex` when its replayed copy arrives.
|
|
100
100
|
return seg.streamSeq !== undefined ? `ticket-event:${seg.streamSeq}` : null;
|
|
101
101
|
default:
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* A seq'd ticket event meeting a HYDRATED twin of itself: history stamps the
|
|
108
|
+
* row's `lastChunkStreamSeq` onto the segment only for standalone rows, and
|
|
109
|
+
* rows persisted before that field existed hydrate seq-less - so the
|
|
110
|
+
* `ticket-event:<seq>` key alone cannot join them, and the replayed copy used
|
|
111
|
+
* to render as a second identical card. Mirror of `addTicketEvent`'s payload
|
|
112
|
+
* fallback: consider only the LAST ticket_event (older ones may be a genuinely
|
|
113
|
+
* repeated resolve/reopen cycle) and never collapse two distinct known seqs.
|
|
114
|
+
*/
|
|
115
|
+
function seqlessTicketEventTwinIndex(segments: MessageSegment[], seg: MessageSegment): number {
|
|
116
|
+
if (seg.type !== 'ticket_event') return -1;
|
|
117
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
118
|
+
const s = segments[i];
|
|
119
|
+
if (s.type !== 'ticket_event') continue;
|
|
120
|
+
if (
|
|
121
|
+
s.streamSeq === undefined &&
|
|
122
|
+
s.data.kind === seg.data.kind &&
|
|
123
|
+
s.data.actorId === seg.data.actorId &&
|
|
124
|
+
s.data.actorName === seg.data.actorName &&
|
|
125
|
+
s.data.actorType === seg.data.actorType &&
|
|
126
|
+
s.data.reason === seg.data.reason &&
|
|
127
|
+
s.data.targetStatusKind === seg.data.targetStatusKind
|
|
128
|
+
) {
|
|
129
|
+
return i;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* First-known time wins, exactly like `addTicketEvent`: the hydrated twin
|
|
138
|
+
* carries the row's real `createdAt`, while a replay only knows its (late)
|
|
139
|
+
* arrival time - replacing wholesale would regress the card to arrival time.
|
|
140
|
+
* Non-ticket-event upserts keep their replace-wholesale semantics.
|
|
141
|
+
*/
|
|
142
|
+
function withFirstKnownOccurredAt(existing: MessageSegment, incoming: MessageSegment): MessageSegment {
|
|
143
|
+
if (existing.type !== 'ticket_event' || incoming.type !== 'ticket_event') return incoming;
|
|
144
|
+
const occurredAt = existing.occurredAt ?? incoming.occurredAt;
|
|
145
|
+
return occurredAt !== undefined ? { ...incoming, occurredAt } : incoming;
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
/**
|
|
107
149
|
* Append-mode counterpart of `updateTrailingAssistant` for post-MESSAGE_END
|
|
108
150
|
* continuation fragments (`SegmentsUpdateMetadata.append`). Coalesces
|
|
@@ -130,8 +172,9 @@ export function appendToTrailingAssistant(
|
|
|
130
172
|
// Block deltas must be IDEMPOTENT: an emit can be seen twice (live plus
|
|
131
173
|
// the JetStream catch-up replay over hydrated history), so upsert on the
|
|
132
174
|
// block's identity instead of raw-appending a duplicate card.
|
|
133
|
-
|
|
134
|
-
if (idx
|
|
175
|
+
let idx = merged.findIndex(m => upsertKey(m) === key);
|
|
176
|
+
if (idx === -1) idx = seqlessTicketEventTwinIndex(merged, seg);
|
|
177
|
+
if (idx !== -1) merged[idx] = withFirstKnownOccurredAt(merged[idx], seg);
|
|
135
178
|
else merged.push(seg);
|
|
136
179
|
} else {
|
|
137
180
|
// Other non-text segments are pushed RAW on purpose: EXECUTING↔EXECUTED
|
|
@@ -315,6 +315,7 @@ function applyHistoryEvent(
|
|
|
315
315
|
escalatedApprovals?: EscalatedApprovals,
|
|
316
316
|
offerResolutions?: OfferResolutions,
|
|
317
317
|
rowCreatedAt?: Date,
|
|
318
|
+
rowStreamSeq?: number,
|
|
318
319
|
): void {
|
|
319
320
|
// batchApprovalsEnabled is owned by the consumer (oss-tenant chat client /
|
|
320
321
|
// openframe-frontend tickets). Defaults to ON so consumers that haven't
|
|
@@ -342,12 +343,16 @@ function applyHistoryEvent(
|
|
|
342
343
|
});
|
|
343
344
|
break;
|
|
344
345
|
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
346
|
+
// `rowStreamSeq` re-stamps the event's JetStream sequence (the persisted
|
|
347
|
+
// row's `lastChunkStreamSeq` - `messageData` itself does not carry it).
|
|
348
|
+
// The store's `ticket-event:<seq>` upsert key is the ONLY join between
|
|
349
|
+
// this hydrated segment and a catch-up/stale-consumer replay of the same
|
|
350
|
+
// chunk; a seq-less segment never joins, so the replayed copy rendered as
|
|
351
|
+
// a second identical card (the resolve/reopen duplication). The payload
|
|
352
|
+
// fallback in `addTicketEvent` cannot own that case: a COMPLETE hydrated
|
|
353
|
+
// tail seeds no accumulator, so the twins only ever meet in the store.
|
|
349
354
|
case 'ticket-event':
|
|
350
|
-
// `rowCreatedAt` is the persisted row's own time
|
|
355
|
+
// `rowCreatedAt` is the persisted row's own time - without it the card
|
|
351
356
|
// renders the enclosing assistant bubble's timestamp, i.e. the FIRST
|
|
352
357
|
// row of the turn, and every lifecycle card reads the same stale time.
|
|
353
358
|
accumulator.addTicketEvent(
|
|
@@ -359,7 +364,7 @@ function applyHistoryEvent(
|
|
|
359
364
|
reason: event.reason,
|
|
360
365
|
targetStatusKind: event.targetStatusKind,
|
|
361
366
|
},
|
|
362
|
-
|
|
367
|
+
rowStreamSeq,
|
|
363
368
|
rowCreatedAt,
|
|
364
369
|
);
|
|
365
370
|
break;
|
|
@@ -697,6 +702,13 @@ export function processHistoricalMessages(
|
|
|
697
702
|
escalatedApprovals,
|
|
698
703
|
offerResolutions,
|
|
699
704
|
new Date(msg.createdAt),
|
|
705
|
+
// TICKET_EVENT chunks are standalone, one per row, so the row seq IS
|
|
706
|
+
// the event seq - but vouch for that only when the row holds exactly
|
|
707
|
+
// one entry: a bundled row's `lastChunkStreamSeq` belongs to its LAST
|
|
708
|
+
// chunk and could stamp the wrong event.
|
|
709
|
+
messageDataArray.length === 1 && typeof msg.lastChunkStreamSeq === 'number'
|
|
710
|
+
? msg.lastChunkStreamSeq
|
|
711
|
+
: undefined,
|
|
700
712
|
);
|
|
701
713
|
});
|
|
702
714
|
|