@memberjunction/ng-conversations 6.2.0-edge.5 → 6.2.0-edge.6

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.
Files changed (71) hide show
  1. package/dist/lib/components/attachment/image-viewer.component.d.ts +3 -1
  2. package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
  3. package/dist/lib/components/attachment/image-viewer.component.js +5 -1
  4. package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
  5. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +13 -6
  6. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  7. package/dist/lib/components/conversation/conversation-chat-area.component.js +74 -28
  8. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  9. package/dist/lib/components/message/message-input.component.d.ts +60 -20
  10. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  11. package/dist/lib/components/message/message-input.component.js +282 -381
  12. package/dist/lib/components/message/message-input.component.js.map +1 -1
  13. package/dist/lib/components/message/message-item.component.d.ts +16 -1
  14. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  15. package/dist/lib/components/message/message-item.component.js +180 -144
  16. package/dist/lib/components/message/message-item.component.js.map +1 -1
  17. package/dist/lib/components/message/message-list.component.d.ts +5 -0
  18. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  19. package/dist/lib/components/message/message-list.component.js +53 -38
  20. package/dist/lib/components/message/message-list.component.js.map +1 -1
  21. package/dist/lib/components/overlay/chat-overlay.component.d.ts +19 -0
  22. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  23. package/dist/lib/components/overlay/chat-overlay.component.js +125 -13
  24. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  25. package/dist/lib/components/project/project-form-modal.component.js +2 -2
  26. package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
  27. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +6 -1
  28. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  29. package/dist/lib/components/realtime/realtime-agent-banner.component.js +110 -82
  30. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  31. package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts +13 -0
  32. package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts.map +1 -0
  33. package/dist/lib/components/realtime/realtime-call-failure-notice.js +21 -0
  34. package/dist/lib/components/realtime/realtime-call-failure-notice.js.map +1 -0
  35. package/dist/lib/components/realtime/realtime-composer.component.js +114 -102
  36. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  37. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +21 -2
  38. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  39. package/dist/lib/components/realtime/realtime-session-overlay.component.js +113 -76
  40. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  41. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  42. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +7 -1
  43. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  44. package/dist/lib/services/conversation-agent.service.d.ts +37 -12
  45. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  46. package/dist/lib/services/conversation-agent.service.js +51 -27
  47. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  48. package/dist/lib/services/conversation-detail-window.store.d.ts +5 -0
  49. package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
  50. package/dist/lib/services/conversation-detail-window.store.js +52 -5
  51. package/dist/lib/services/conversation-detail-window.store.js.map +1 -1
  52. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  53. package/dist/lib/services/realtime-session.service.js +2 -1
  54. package/dist/lib/services/realtime-session.service.js.map +1 -1
  55. package/dist/lib/utils/agent-failure-display.d.ts +14 -0
  56. package/dist/lib/utils/agent-failure-display.d.ts.map +1 -0
  57. package/dist/lib/utils/agent-failure-display.js +26 -0
  58. package/dist/lib/utils/agent-failure-display.js.map +1 -0
  59. package/dist/lib/utils/message-list-order.d.ts +13 -0
  60. package/dist/lib/utils/message-list-order.d.ts.map +1 -0
  61. package/dist/lib/utils/message-list-order.js +31 -0
  62. package/dist/lib/utils/message-list-order.js.map +1 -0
  63. package/dist/lib/utils/pin-state.d.ts +11 -0
  64. package/dist/lib/utils/pin-state.d.ts.map +1 -0
  65. package/dist/lib/utils/pin-state.js +9 -0
  66. package/dist/lib/utils/pin-state.js.map +1 -0
  67. package/dist/public-api.d.ts +1 -0
  68. package/dist/public-api.d.ts.map +1 -1
  69. package/dist/public-api.js +1 -0
  70. package/dist/public-api.js.map +1 -1
  71. package/package.json +36 -36
@@ -33,6 +33,8 @@ export class ConversationDetailWindowStore {
33
33
  loadFailed = false;
34
34
  /** The newest `Sequence` the window may hold; null for no bound. */
35
35
  upToSequence = null;
36
+ /** IDs of persisted rows that the loader returned with `Sequence === 0` (legacy rows). */
37
+ serverAssignedZeroSequenceIds = new Set();
36
38
  constructor(loader) {
37
39
  this.loader = loader;
38
40
  }
@@ -53,6 +55,7 @@ export class ConversationDetailWindowStore {
53
55
  this.isLoadingOlder = false;
54
56
  this.loadFailed = false;
55
57
  this.upToSequence = null;
58
+ this.serverAssignedZeroSequenceIds.clear();
56
59
  }
57
60
  /**
58
61
  * The exclusive `BeforeSequence` that keeps a newest-rows read at or below {@link upToSequence};
@@ -135,6 +138,7 @@ export class ConversationDetailWindowStore {
135
138
  // `pageSize` TIMELINE ITEMS here — this is what actually bounds the transcript.
136
139
  const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);
137
140
  const droppedOlderRows = page.Page.length < result.Details.length;
141
+ this.recordServerZeroSequences(page.Page);
138
142
  this.mergeDetails(page.Page);
139
143
  this.peripherals = peripheralsFrom(result);
140
144
  // A failed first read leaves the window empty. `LoadFailed` is what stops that
@@ -194,13 +198,14 @@ export class ConversationDetailWindowStore {
194
198
  // from what came back, not every raw row.
195
199
  const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);
196
200
  const droppedOlderRows = page.Page.length < result.Details.length;
201
+ this.recordServerZeroSequences(page.Page);
197
202
  this.mergeDetails(page.Page);
198
203
  // Older pages bring their OWN artifacts/ratings/runs — merge, never replace,
199
204
  // or paging up would strip the peripherals off the rows already on screen.
200
205
  this.mergePeripherals(result);
201
206
  // Only the upward bound moves — the tail is whatever we already had.
202
207
  this.cursor = {
203
- OldestSequence: this.loadedDetails[0]?.Sequence ?? this.cursor.OldestSequence,
208
+ OldestSequence: oldestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds) ?? this.cursor.OldestSequence,
204
209
  NewestSequence: this.cursor.NewestSequence,
205
210
  HasMoreAbove: result.HasMoreAbove || droppedOlderRows
206
211
  };
@@ -312,13 +317,14 @@ export class ConversationDetailWindowStore {
312
317
  */
313
318
  applyRefreshedTail(tail, previousOldest, previousNewest) {
314
319
  const fetched = tail.Results.flatMap(result => result.Details);
320
+ this.recordServerZeroSequences(fetched);
315
321
  const hasMoreAbove = this.mergeRefreshedRows(tail, fetched, previousOldest, previousNewest);
316
322
  for (const result of tail.Results) {
317
323
  this.mergePeripherals(result);
318
324
  }
319
325
  this.cursor = {
320
- OldestSequence: this.loadedDetails[0]?.Sequence ?? null,
321
- NewestSequence: this.loadedDetails[this.loadedDetails.length - 1]?.Sequence ?? null,
326
+ OldestSequence: oldestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds),
327
+ NewestSequence: newestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds),
322
328
  HasMoreAbove: hasMoreAbove
323
329
  };
324
330
  }
@@ -401,7 +407,7 @@ export class ConversationDetailWindowStore {
401
407
  */
402
408
  ApplyLocalDetail(detail) {
403
409
  this.mergeDetails([detail]);
404
- const newest = this.loadedDetails[this.loadedDetails.length - 1]?.Sequence ?? null;
410
+ const newest = newestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds) ?? this.cursor.NewestSequence;
405
411
  this.cursor = { ...this.cursor, NewestSequence: newest };
406
412
  }
407
413
  /** Removes a row by ID — the delete path. */
@@ -500,9 +506,18 @@ export class ConversationDetailWindowStore {
500
506
  this.peripherals.ArtifactsByDetailId.set(id, artifacts);
501
507
  }
502
508
  }
509
+ recordServerZeroSequences(rows) {
510
+ for (const row of rows) {
511
+ if (row.Sequence === 0) {
512
+ this.serverAssignedZeroSequenceIds.add(NormalizeUUID(row.ID));
513
+ }
514
+ }
515
+ }
503
516
  /**
504
517
  * Union-merges rows into the loaded set: replace-by-ID, then sort by `Sequence`.
505
518
  * Dedupe is required because session expansion can re-return rows a prior page held.
519
+ * Unsaved local placeholders (`Sequence === 0`) have no sequence yet and stay at the tail
520
+ * in insertion order instead of sorting ahead of `Sequence >= 1` rows.
506
521
  */
507
522
  mergeDetails(incoming) {
508
523
  const byId = new Map();
@@ -512,8 +527,40 @@ export class ConversationDetailWindowStore {
512
527
  for (const detail of incoming) {
513
528
  byId.set(NormalizeUUID(detail.ID), detail);
514
529
  }
515
- this.loadedDetails = [...byId.values()].sort((a, b) => a.Sequence - b.Sequence);
530
+ this.loadedDetails = [...byId.values()].sort((a, b) => {
531
+ const aAssigned = hasAssignedSequence(a, this.serverAssignedZeroSequenceIds);
532
+ const bAssigned = hasAssignedSequence(b, this.serverAssignedZeroSequenceIds);
533
+ if (aAssigned && bAssigned) {
534
+ return a.Sequence - b.Sequence;
535
+ }
536
+ if (aAssigned !== bAssigned) {
537
+ return aAssigned ? -1 : 1;
538
+ }
539
+ return 0;
540
+ });
541
+ }
542
+ }
543
+ /** True when a row has a persisted `Sequence` (unsaved local placeholders have `Sequence === 0` and are not in `serverZeroIds`). */
544
+ function hasAssignedSequence(detail, serverZeroIds) {
545
+ return typeof detail.Sequence === 'number' && (detail.Sequence > 0 || (detail.Sequence === 0 && serverZeroIds.has(NormalizeUUID(detail.ID))));
546
+ }
547
+ /** The smallest assigned `Sequence` in a sorted loaded-details slice, or `null` when none is assigned. */
548
+ function oldestAssignedSequence(details, serverZeroIds) {
549
+ for (let i = 0; i < details.length; i++) {
550
+ if (hasAssignedSequence(details[i], serverZeroIds)) {
551
+ return details[i].Sequence;
552
+ }
553
+ }
554
+ return null;
555
+ }
556
+ /** The largest assigned `Sequence` in a sorted loaded-details slice, or `null` when none is assigned. */
557
+ function newestAssignedSequence(details, serverZeroIds) {
558
+ for (let i = details.length - 1; i >= 0; i--) {
559
+ if (hasAssignedSequence(details[i], serverZeroIds)) {
560
+ return details[i].Sequence;
561
+ }
516
562
  }
563
+ return null;
517
564
  }
518
565
  /** Empty peripheral maps — the reset / no-window state. */
519
566
  function emptyPeripherals() {
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-detail-window.store.js","sourceRoot":"","sources":["../../../src/lib/services/conversation-detail-window.store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAOvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,EAEH,wBAAwB,EACxB,4BAA4B,EAC5B,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC7B,MAAM,qCAAqC,CAAC;AAqE7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,6BAA6B;IAC9B,MAAM,CAAqB;IAC3B,cAAc,GAAkB,IAAI,CAAC;IACrC,QAAQ,GAAkB,IAAI,CAAC;IAC/B,aAAa,GAAiC,EAAE,CAAC;IACjD,aAAa,GAAiC,EAAE,CAAC;IACjD,gBAAgB,GAAG,CAAC,CAAC;IACrB,WAAW,GAAwC,gBAAgB,EAAE,CAAC;IACtE,MAAM,GAAmC,WAAW,EAAE,CAAC;IACvD,UAAU,GAAG,CAAC,CAAC;IACf,eAAe,GAAG,KAAK,CAAC;IACxB,cAAc,GAAG,KAAK,CAAC;IACvB,UAAU,GAAG,KAAK,CAAC;IAC3B,oEAAoE;IAC5D,YAAY,GAAkB,IAAI,CAAC;IAE3C,YAAY,MAA0B;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,cAA6B,EAAE,WAA0B,IAAI;QACtE,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,gBAAgB,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;QAC5B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACK,eAAe;QACnB,OAAO,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IACzE,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,wBAAwB,CAClC,MAA8B,EAC9B,WAAqB;QAErB,IAAI,QAAQ,GAAG,oBAAoB,CAAC;QACpC,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,WAAW,CAAC,CAAC;QAEnG,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,qBAAqB,EAAE,OAAO,EAAE,EAAE,CAAC;YAC/D,MAAM,SAAS,GAAG,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YACnE,IAAI,SAAS,IAAI,4BAA4B,EAAE,CAAC;gBAC5C,MAAM,CAAsB,uCAAuC;YACvE,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,CAAsB,0CAA0C;YAC1E,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM,CAAsB,gDAAgD;YAChF,CAAC;YACD,gFAAgF;YAChF,iFAAiF;YACjF,mFAAmF;YACnF,oFAAoF;YACpF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;gBACnC,MAAM;YACV,CAAC;YAED,QAAQ,IAAI,sBAAsB,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,WAAW,CAAC,CAAC;YACpG,8EAA8E;YAC9E,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC1D,MAAM;YACV,CAAC;YACD,MAAM,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;;;;;;;OASG;IACI,KAAK,CAAC,UAAU,CACnB,cAAsB,EACtB,WAAqB,EACrB,WAA0B,IAAI,EAC9B,eAA8B,IAAI;QAElC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAE5B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAC9C;gBACI,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,4BAA4B;gBACtC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;aAC9D,EACD,WAAW,CACd,CAAC;YAEF,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,OAAO,CAAG,yCAAyC;YACvD,CAAC;YACD,+EAA+E;YAC/E,4EAA4E;YAC5E,gFAAgF;YAChF,MAAM,IAAI,GAAG,wBAAwB,CAAC,MAAM,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;YACpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAElE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,WAAW,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YAC3C,+EAA+E;YAC/E,2EAA2E;YAC3E,+EAA+E;YAC/E,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;YAChC,IAAI,CAAC,MAAM,GAAG;gBACV,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,IAAI,MAAM,CAAC,cAAc;gBACtE,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,8EAA8E;gBAC9E,6EAA6E;gBAC7E,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,gBAAgB;aACxD,CAAC;QACN,CAAC;gBAAS,CAAC;YACP,4DAA4D;YAC5D,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YACjC,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,SAAS,CAAC,WAAqB;QACxC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,MAAM,cAAc,GAAG,IAAI,CAAC,cAAwB,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,cAAwB,CAAC;QACpD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAE3B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAC9C;gBACI,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,cAAc,EAAE,MAAM;gBACtB,QAAQ,EAAE,4BAA4B;aACzC,EACD,WAAW,CACd,CAAC;YAEF,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,OAAO;YACX,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;YAChC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,6EAA6E;gBAC7E,4EAA4E;gBAC5E,6EAA6E;gBAC7E,6EAA6E;gBAC7E,4EAA4E;gBAC5E,OAAO;YACX,CAAC;YAED,6EAA6E;YAC7E,0CAA0C;YAC1C,MAAM,IAAI,GAAG,wBAAwB,CAAC,MAAM,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;YACpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAElE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,6EAA6E;YAC7E,2EAA2E;YAC3E,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YAC9B,qEAAqE;YACrE,IAAI,CAAC,MAAM,GAAG;gBACV,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc;gBAC7E,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;gBAC1C,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,gBAAgB;aACxD,CAAC;QACN,CAAC;gBAAS,CAAC;YACP,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;YAChC,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;;;;;;OAWG;IACI,KAAK,CAAC,aAAa,CAAC,WAAqB;QAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;QAC3C,IAAI,CAAC,cAAc,EAAE,CAAC;YAClB,OAAO;QACX,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;QAClD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;QAClD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAE5B,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,kBAAkB,CACtC,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,CAC1D,CAAC;YACF,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,OAAO;YACX,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;YAC9B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,+EAA+E;gBAC/E,4EAA4E;gBAC5E,8EAA8E;gBAC9E,qEAAqE;gBACrE,2EAA2E;gBAC3E,iCAAiC;gBACjC,OAAO;YACX,CAAC;YACD,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;QAClE,CAAC;gBAAS,CAAC;YACP,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YACjC,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,CAAC,kBAAkB,CAC5B,cAAsB,EACtB,cAA6B,EAC7B,UAAkB,EAClB,WAAqB;QAErB,MAAM,OAAO,GAA6B,EAAE,CAAC;QAC7C,IAAI,MAAM,GAAuB,IAAI,CAAC,eAAe,EAAE,CAAC;QAExD,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,0BAA0B,EAAE,IAAI,EAAE,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAC7C;gBACI,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,cAAc,EAAE,MAAM;gBACtB,QAAQ,EAAE,4BAA4B;aACzC,EACD,WAAW,CACd,CAAC;YACF,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,YAAY;YACxF,CAAC;YACD,oFAAoF;YACpF,iFAAiF;YACjF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAChB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YAC1E,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAErB,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC;gBAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YAC1E,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,cAAc,IAAI,SAAS,CAAC;QAChD,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3E,CAAC;IAED,6FAA6F;IACrF,YAAY,CAAC,MAA8B,EAAE,cAA6B;QAC9E,kFAAkF;QAClF,IAAI,cAAc,KAAK,IAAI,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5D,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,2CAA2C;QAC3C,IAAI,MAAM,CAAC,cAAc,IAAI,cAAc,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,qFAAqF;QACrF,8EAA8E;QAC9E,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;IAChC,CAAC;IAED;;;OAGG;IACK,kBAAkB,CACtB,IAAmB,EACnB,cAA6B,EAC7B,cAA6B;QAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;QAE5F,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,MAAM,GAAG;YACV,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI;YACvD,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI;YACnF,YAAY,EAAE,YAAY;SAC7B,CAAC;IACN,CAAC;IAED,8FAA8F;IACtF,kBAAkB,CACtB,IAAmB,EACnB,OAAqC,EACrC,cAA6B,EAC7B,cAA6B;QAE7B,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC1B,kFAAkF;YAClF,kCAAkC;YAClC,MAAM,IAAI,GAAG,wBAAwB,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;YAC7E,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACnD,OAAO,CAAC,IAAI,EAAE,YAAY,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9E,CAAC;QAED,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,gFAAgF;YAChF,iFAAiF;YACjF,iFAAiF;YACjF,kFAAkF;YAClF,8DAA8D;YAC9D,IAAI,CAAC,YAAY,CAAC,cAAc,KAAK,IAAI;gBACrC,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC,CAAC;YACnE,iFAAiF;YACjF,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;QACpC,CAAC;QAED,oFAAoF;QACpF,oFAAoF;QACpF,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,IAAW,YAAY;QACnB,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;IACpC,CAAC;IAED,IAAW,cAAc;QACrB,OAAO,IAAI,CAAC,cAAc,CAAC;IAC/B,CAAC;IAED,IAAW,eAAe;QACtB,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;IAED,IAAW,gBAAgB;QACvB,OAAO,IAAI,CAAC,gBAAgB,CAAC;IACjC,CAAC;IAED,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IAED,oFAAoF;IACpF,IAAW,UAAU;QACjB,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED,iEAAiE;IACjE,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,sFAAsF;IAC/E,YAAY;QACf,OAAO,IAAI,CAAC,cAAc,KAAK,IAAI;eAC5B,IAAI,CAAC,MAAM,CAAC,YAAY;eACxB,IAAI,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI;eACnC,CAAC,IAAI,CAAC,cAAc;eACpB,CAAC,IAAI,CAAC,eAAe,CAAC;IACjC,CAAC;IAED;;;OAGG;IACI,gBAAgB,CAAC,MAAkC;QACtD,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;QACnF,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IAC7D,CAAC;IAED,6CAA6C;IACtC,YAAY,CAAC,QAAgB;QAChC,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;QACjF,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;QAC/C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;QACjF,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;YAC3C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC;QACnE,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACI,cAAc,CAAC,KAAa;QAC/B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,qFAAqF;IAC9E,gBAAgB,CAAC,IAAkC;QACtD,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/B,iFAAiF;QACjF,mFAAmF;QACnF,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,CAAC;IAED;;;;;OAKG;IACI,aAAa,CAAC,MAAkC;QACnD,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;QAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;QAC5E,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAEtE,kFAAkF;QAClF,oFAAoF;QACpF,kFAAkF;QAClF,gFAAgF;QAChF,IAAI,MAAM,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC5B,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,SAAS,EAAE,CAAC;YACvC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC;QACnE,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACI,WAAW;QACd,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,OAAO;YACH,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,OAAO,EAAE,OAAO;YAChB,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;YAC1B,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;YACtC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,+EAA+E;YAC/E,6EAA6E;YAC7E,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,mBAAmB;YACzD,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW;YACzC,iBAAiB,EAAE,IAAI,CAAC,WAAW,CAAC,iBAAiB;YACrD,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,mBAAmB;YACzD,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,UAAU,EAAE,IAAI,CAAC,UAAU;SAC9B,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACK,gBAAgB,CAAC,QAA6C;QAClE,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YACnD,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC9C,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YACrD,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,QAAQ,CAAC,mBAAmB,EAAE,CAAC;YACzD,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC5D,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,QAAsC;QACvD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAsC,CAAC;QAC3D,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpF,CAAC;CACJ;AAED,2DAA2D;AAC3D,SAAS,gBAAgB;IACrB,OAAO;QACH,mBAAmB,EAAE,IAAI,GAAG,EAAE;QAC9B,WAAW,EAAE,IAAI,GAAG,EAAE;QACtB,iBAAiB,EAAE,IAAI,GAAG,EAAE;QAC5B,mBAAmB,EAAE,IAAI,GAAG,EAAE;KACjC,CAAC;AACN,CAAC;AAED,kGAAkG;AAClG,SAAS,eAAe,CAAC,MAA8B;IACnD,OAAO;QACH,mBAAmB,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC;QACxD,WAAW,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;QACxC,iBAAiB,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC;QACpD,mBAAmB,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC;KAC3D,CAAC;AACN,CAAC;AAED,+BAA+B;AAC/B,SAAS,WAAW;IAChB,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED,2FAA2F;AAC3F,6FAA6F;AAC7F,2FAA2F;AAC3F,yCAAyC","sourcesContent":["import { NormalizeUUID } from '@memberjunction/global';\nimport type { UserInfo } from '@memberjunction/core';\nimport type {\n MJConversationDetailEntity,\n LoadDetailWindowParams,\n DetailWindowLoadResult\n} from '@memberjunction/core-entities';\nimport { BuildConversationTimeline } from '../utils/realtime-session-timeline';\nimport {\n ConversationDetailWindowCursor,\n SelectLatestTimelinePage,\n DEFAULT_TRANSCRIPT_PAGE_SIZE,\n DEFAULT_RAW_OVERREAD,\n MAX_OVERREAD_ATTEMPTS,\n OVERREAD_GROWTH_FACTOR,\n MAX_REFRESH_BACKFILL_PAGES\n} from '../utils/conversation-detail-window';\n\n/**\n * The engine surface this store needs. Declared structurally rather than importing\n * ConversationEngine so tests can pass a plain `{ LoadDetailWindow: vi.fn() }` with no\n * TestBed and no engine bootstrap.\n */\nexport interface DetailWindowLoader {\n LoadDetailWindow(params: LoadDetailWindowParams, contextUser: UserInfo): Promise<DetailWindowLoadResult>;\n}\n\n/**\n * Peripheral data for the loaded window, keyed the way the chat area already consumes it.\n *\n * Accumulates across pages: an older page brings its own artifacts and ratings, which merge\n * into these maps rather than replacing them.\n */\nexport type ConversationDetailWindowPeripherals = Pick<\n DetailWindowLoadResult,\n 'AgentRunsByDetailId' | 'UserAvatars' | 'RatingsByDetailId' | 'ArtifactsByDetailId'\n>;\n\n/**\n * What a tail refresh read, and whether it managed to reach back to the rows already loaded.\n *\n * `ReachedPreviousTail: false` is the \"the conversation ran away from us\" case — see\n * {@link MAX_REFRESH_BACKFILL_PAGES}. It is the only signal that the loaded set and the fresh\n * set do not join up, so it must not be dropped between the read and the merge.\n */\ninterface RefreshedTail {\n Results: DetailWindowLoadResult[];\n ReachedPreviousTail: boolean;\n /**\n * True when any read in the bridge failed.\n *\n * Kept separate from `ReachedPreviousTail: false`, which means \"the tail is genuinely\n * further away than the budget allows\" and legitimately discards the disconnected older\n * rows. A FAILED read must never trigger that — throwing away a reader's loaded history\n * because one query blipped is not a recovery, it is the bug wearing a different hat.\n */\n Failed: boolean;\n}\n\n/** What the chat area binds to after any store operation. */\nexport interface ConversationDetailWindowSnapshot extends ConversationDetailWindowPeripherals {\n ConversationID: string | null;\n /** Loaded rows, chronological by Sequence. A NEW array each call, so ngOnChanges fires. */\n Details: MJConversationDetailEntity[];\n Cursor: ConversationDetailWindowCursor;\n /** Pinned rows for the pins panel, INCLUDING pins older than the window. */\n PinnedDetails: MJConversationDetailEntity[];\n /**\n * TRUE total pins in the conversation, which is NOT `PinnedDetails.length` until the\n * panel has been opened — the open path reads only the count so the chip has a number\n * without hydrating anything.\n */\n PinnedTotalCount: number;\n IsLoadingLatest: boolean;\n IsLoadingOlder: boolean;\n /**\n * True when the most recent load FAILED, as opposed to returning nothing.\n *\n * Exists so an empty transcript can be told apart from a broken one. Without it a failed\n * first read renders as a conversation with no messages, which reads to the user as \"my\n * conversation is gone\" — the one failure mode worse than an error message.\n */\n LoadFailed: boolean;\n}\n\n/**\n * Holds ONE conversation's loaded transcript window and the cursors for paging further back.\n *\n * Owned per chat-area instance (not a singleton) so two hosts can never share window state.\n *\n * Contracts:\n * - {@link Reset} bumps a generation counter; every in-flight load checks it after each\n * await and discards its result if the conversation changed underneath.\n * - {@link LoadOlder} is a no-op while a load is in flight or when nothing is above.\n * - A window loaded with an `upToSequence` ({@link LoadLatest}) holds only rows at or below it;\n * every later read keeps that bound.\n * - Merges dedupe by normalized ID and re-sort by `Sequence` — session expansion can\n * return rows an earlier page already had.\n * - {@link ApplyLocalDetail} never hits the network; it is the send/stream/edit path.\n * - Pins live in their own array. They are NOT unioned into the transcript, because a pin\n * below the window's oldest Sequence would break contiguity and the paging cursor.\n */\nexport class ConversationDetailWindowStore {\n private loader: DetailWindowLoader;\n private conversationId: string | null = null;\n private branchId: string | null = null;\n private loadedDetails: MJConversationDetailEntity[] = [];\n private pinnedDetails: MJConversationDetailEntity[] = [];\n private pinnedTotalCount = 0;\n private peripherals: ConversationDetailWindowPeripherals = emptyPeripherals();\n private cursor: ConversationDetailWindowCursor = emptyCursor();\n private generation = 0;\n private isLoadingLatest = false;\n private isLoadingOlder = false;\n private loadFailed = false;\n /** The newest `Sequence` the window may hold; null for no bound. */\n private upToSequence: number | null = null;\n\n constructor(loader: DetailWindowLoader) {\n this.loader = loader;\n }\n\n /**\n * Drops all state and invalidates in-flight loads. Call on every conversation switch.\n * `branchId` is the branch whose path every later load pages; null is the trunk.\n */\n public Reset(conversationId: string | null, branchId: string | null = null): void {\n this.generation++;\n this.conversationId = conversationId;\n this.branchId = branchId;\n this.loadedDetails = [];\n this.pinnedDetails = [];\n this.pinnedTotalCount = 0;\n this.peripherals = emptyPeripherals();\n this.cursor = emptyCursor();\n this.isLoadingLatest = false;\n this.isLoadingOlder = false;\n this.loadFailed = false;\n this.upToSequence = null;\n }\n\n /**\n * The exclusive `BeforeSequence` that keeps a newest-rows read at or below {@link upToSequence};\n * undefined when the window has no bound. `Sequence` is an int column, so `< n + 1` is `<= n`.\n */\n private newestReadBound(): number | undefined {\n return this.upToSequence == null ? undefined : this.upToSequence + 1;\n }\n\n /**\n * Fetches a page, WIDENING the raw over-read until it yields a full page of timeline\n * items (or the conversation runs out).\n *\n * Raw rows are not display items: a realtime session folds many stamped rows into ONE\n * timeline card, so the default `3 × pageSize` over-read can come back holding two or\n * three items instead of ten. Taking that at face value gives the reader a stubby page\n * and makes the sentinel fire again immediately — which is worse than one wider read.\n *\n * Bounded by {@link MAX_OVERREAD_ATTEMPTS}: a conversation that is almost entirely one\n * long session would otherwise grow the read until it pulled the whole table.\n */\n private async fetchPageFillingTimeline(\n params: LoadDetailWindowParams,\n contextUser: UserInfo\n ): Promise<DetailWindowLoadResult> {\n let overread = DEFAULT_RAW_OVERREAD;\n let result = await this.loader.LoadDetailWindow({ ...params, RawOverread: overread }, contextUser);\n\n for (let attempt = 1; attempt < MAX_OVERREAD_ATTEMPTS; attempt++) {\n const itemCount = BuildConversationTimeline(result.Details).length;\n if (itemCount >= DEFAULT_TRANSCRIPT_PAGE_SIZE) {\n break; // already a full page of display items\n }\n if (result.Failed) {\n break; // widening a failed read just fails again\n }\n if (!result.HasMoreAbove) {\n break; // start of the conversation — short IS complete\n }\n // The decisive test: did the read actually HIT its row limit? A fetch that came\n // back under its own `MaxRows` has already returned everything available in that\n // range, so a wider read would return the identical rows. Only a fetch that filled\n // its limit can have been truncated by collapse, and only that is worth re-reading.\n if (result.Details.length < overread) {\n break;\n }\n\n overread *= OVERREAD_GROWTH_FACTOR;\n const wider = await this.loader.LoadDetailWindow({ ...params, RawOverread: overread }, contextUser);\n // A failed or non-productive retry must not discard the page we already have.\n if (!wider || wider.Details.length <= result.Details.length) {\n break;\n }\n result = wider;\n }\n return result;\n }\n\n /**\n * First paint: the newest page of a conversation's path.\n *\n * With `upToSequence`, the window holds only rows at or below that `Sequence`: the first page\n * ends there, and every later read of the window keeps the bound. Older pages load as usual.\n *\n * The loader is not expected to throw — `LoadDetailWindow` logs and returns an empty\n * window on a failed read — but a transport-layer rejection would otherwise strand\n * `isLoadingLatest` at true and wedge the store, so the flag is cleared in `finally`.\n */\n public async LoadLatest(\n conversationId: string,\n contextUser: UserInfo,\n branchId: string | null = null,\n upToSequence: number | null = null\n ): Promise<void> {\n this.Reset(conversationId, branchId);\n this.upToSequence = upToSequence;\n const generation = this.generation;\n this.isLoadingLatest = true;\n\n try {\n const before = this.newestReadBound();\n const result = await this.fetchPageFillingTimeline(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE,\n ...(before === undefined ? {} : { BeforeSequence: before })\n },\n contextUser\n );\n\n if (generation !== this.generation) {\n return; // user switched conversations mid-flight\n }\n // The engine OVER-READS raw rows (a page of N rows can collapse to one session\n // card), so the fetch returns more than a page's worth. Slice to the newest\n // `pageSize` TIMELINE ITEMS here — this is what actually bounds the transcript.\n const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n const droppedOlderRows = page.Page.length < result.Details.length;\n\n this.mergeDetails(page.Page);\n this.peripherals = peripheralsFrom(result);\n // A failed first read leaves the window empty. `LoadFailed` is what stops that\n // rendering as \"this conversation has no messages\" — there is no cursor to\n // protect here (an empty window cannot page), so the flag IS the whole remedy.\n this.loadFailed = result.Failed;\n this.cursor = {\n OldestSequence: page.OldestIncluded?.Sequence ?? result.OldestSequence,\n NewestSequence: result.NewestSequence,\n // Rows the slice discarded are older content that IS available — the engine's\n // probe only knows about rows below what it fetched, not below what we kept.\n HasMoreAbove: result.HasMoreAbove || droppedOlderRows\n };\n } finally {\n // Guarded: a stale load must not clear a newer load's flag.\n if (generation === this.generation) {\n this.isLoadingLatest = false;\n }\n }\n }\n\n /**\n * Prepends the next older page. No-op when nothing is above or a load is running.\n *\n * `isLoadingOlder` is cleared in `finally` for the same reason as {@link LoadLatest} —\n * and it matters more here, because a stuck flag makes {@link CanLoadOlder} false\n * forever, which silently kills the transcript's \"earlier messages\" sentinel.\n */\n public async LoadOlder(contextUser: UserInfo): Promise<void> {\n if (!this.CanLoadOlder()) {\n return;\n }\n const generation = this.generation;\n const conversationId = this.conversationId as string;\n const before = this.cursor.OldestSequence as number;\n this.isLoadingOlder = true;\n\n try {\n const result = await this.fetchPageFillingTimeline(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n BeforeSequence: before,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE\n },\n contextUser\n );\n\n if (generation !== this.generation) {\n return;\n }\n this.loadFailed = result.Failed;\n if (result.Failed) {\n // Leave the cursor EXACTLY as it was. Folding a failed read's `HasMoreAbove:\n // false` into it would retire the \"earlier messages\" sentinel permanently —\n // the template unmounts it, the observer disconnects, and no later call ever\n // sets the flag back, so one transport blip costs the reader the rest of the\n // conversation's history with nothing said. The next sentinel fire retries.\n return;\n }\n\n // Same over-read slice as LoadLatest: keep the newest page of timeline items\n // from what came back, not every raw row.\n const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n const droppedOlderRows = page.Page.length < result.Details.length;\n\n this.mergeDetails(page.Page);\n // Older pages bring their OWN artifacts/ratings/runs — merge, never replace,\n // or paging up would strip the peripherals off the rows already on screen.\n this.mergePeripherals(result);\n // Only the upward bound moves — the tail is whatever we already had.\n this.cursor = {\n OldestSequence: this.loadedDetails[0]?.Sequence ?? this.cursor.OldestSequence,\n NewestSequence: this.cursor.NewestSequence,\n HasMoreAbove: result.HasMoreAbove || droppedOlderRows\n };\n } finally {\n if (generation === this.generation) {\n this.isLoadingOlder = false;\n }\n }\n }\n\n /**\n * Re-fetches the tail and folds it into the existing window.\n *\n * The refresh-in-place counterpart to {@link LoadLatest}: used when something changed at\n * the tail (an agent finished, artifacts were written) and the transcript needs to pick\n * it up. Unlike `LoadLatest` it does NOT `Reset`, so a user who has paged up five times\n * keeps those pages instead of being yanked back to a 10-row tail.\n *\n * Reads via {@link fetchRefreshedTail}, which keeps paging down until it reaches rows the\n * window already holds — otherwise a conversation that grew by more than a page leaves a\n * hole in the middle of the merged set that nothing in the UI can reveal.\n */\n public async RefreshLatest(contextUser: UserInfo): Promise<void> {\n const conversationId = this.conversationId;\n if (!conversationId) {\n return;\n }\n const generation = this.generation;\n const previousOldest = this.cursor.OldestSequence;\n const previousNewest = this.cursor.NewestSequence;\n this.isLoadingLatest = true;\n\n try {\n const tail = await this.fetchRefreshedTail(\n conversationId, previousNewest, generation, contextUser\n );\n if (generation !== this.generation) {\n return;\n }\n this.loadFailed = tail.Failed;\n if (tail.Failed) {\n // Apply NOTHING. A bridge only reaches a second page because the first did not\n // join, so a failure part-way through means the fresh rows cannot be merged\n // without opening the very gap this method exists to close — and the fallback\n // for an unbridgeable tail discards loaded history, which is far too\n // destructive a response to a transient read error. Leave the window as it\n // was; the next refresh retries.\n return;\n }\n this.applyRefreshedTail(tail, previousOldest, previousNewest);\n } finally {\n if (generation === this.generation) {\n this.isLoadingLatest = false;\n }\n }\n }\n\n /**\n * Reads the newest page, then keeps paging down until the read reaches back to\n * `previousNewest` — the newest row the window already holds.\n *\n * One page is enough in the normal case: a refresh usually returns rows we already have\n * plus the handful that arrived since. The loop is for the case that is not normal, where\n * the tail has moved further than a page and a single read would land entirely above the\n * loaded set.\n *\n * @returns Every page read, plus whether the reads joined up with the loaded window.\n */\n private async fetchRefreshedTail(\n conversationId: string,\n previousNewest: number | null,\n generation: number,\n contextUser: UserInfo\n ): Promise<RefreshedTail> {\n const results: DetailWindowLoadResult[] = [];\n let before: number | undefined = this.newestReadBound();\n\n for (let page = 0; page <= MAX_REFRESH_BACKFILL_PAGES; page++) {\n const result = await this.loader.LoadDetailWindow(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n BeforeSequence: before,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE\n },\n contextUser\n );\n if (generation !== this.generation) {\n return { Results: results, ReachedPreviousTail: false, Failed: false }; // discarded\n }\n // Checked BEFORE tailIsJoined, which reads `HasMoreAbove` — false on a failed read,\n // which would report the bridge as joined and hand back a set with a hole in it.\n if (result.Failed) {\n return { Results: results, ReachedPreviousTail: false, Failed: true };\n }\n results.push(result);\n\n if (this.tailIsJoined(result, previousNewest)) {\n return { Results: results, ReachedPreviousTail: true, Failed: false };\n }\n before = result.OldestSequence ?? undefined;\n }\n return { Results: results, ReachedPreviousTail: false, Failed: false };\n }\n\n /** True when this page reaches back into the loaded window, so nothing sits between them. */\n private tailIsJoined(result: DetailWindowLoadResult, previousNewest: number | null): boolean {\n // Nothing loaded to join up WITH, or the read came back empty — either way, done.\n if (previousNewest === null || result.OldestSequence === null) {\n return true;\n }\n // The page overlaps or abuts what we hold.\n if (result.OldestSequence <= previousNewest) {\n return true;\n }\n // The engine's probe says no row exists below this page at all. The rows we hold are\n // below it, so they have since been deleted — there is no gap left to bridge.\n return !result.HasMoreAbove;\n }\n\n /**\n * Folds a refreshed tail into the window, choosing what to keep by whether the read\n * joined up with the rows already loaded.\n */\n private applyRefreshedTail(\n tail: RefreshedTail,\n previousOldest: number | null,\n previousNewest: number | null\n ): void {\n const fetched = tail.Results.flatMap(result => result.Details);\n const hasMoreAbove = this.mergeRefreshedRows(tail, fetched, previousOldest, previousNewest);\n\n for (const result of tail.Results) {\n this.mergePeripherals(result);\n }\n this.cursor = {\n OldestSequence: this.loadedDetails[0]?.Sequence ?? null,\n NewestSequence: this.loadedDetails[this.loadedDetails.length - 1]?.Sequence ?? null,\n HasMoreAbove: hasMoreAbove\n };\n }\n\n /** Merges the refreshed rows per the three refresh cases. @returns the new `HasMoreAbove`. */\n private mergeRefreshedRows(\n tail: RefreshedTail,\n fetched: MJConversationDetailEntity[],\n previousOldest: number | null,\n previousNewest: number | null\n ): boolean {\n if (previousNewest === null) {\n // Nothing loaded — this is a first paint wearing a refresh's name, so bound it to\n // a page the way LoadLatest does.\n const page = SelectLatestTimelinePage(fetched, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n this.mergeDetails(page.Page);\n const last = tail.Results[tail.Results.length - 1];\n return (last?.HasMoreAbove ?? false) || page.Page.length < fetched.length;\n }\n\n if (tail.ReachedPreviousTail) {\n // Keep EVERY fetched row down to the window's existing floor. Slicing to a page\n // here is what opened the hole: the read deliberately spans the distance back to\n // the loaded tail, and discarding its middle throws away the very rows that make\n // the merged set contiguous. Rows below the floor are dropped so a refresh cannot\n // quietly grow the window downward — that is LoadOlder's job.\n this.mergeDetails(previousOldest === null\n ? fetched\n : fetched.filter(detail => detail.Sequence >= previousOldest));\n // The floor did not move, so what is below it is still whatever we last learned.\n return this.cursor.HasMoreAbove;\n }\n\n // Could not bridge within the page budget. Drop the now-disconnected older rows and\n // keep the fresh tail: a visible gap the sentinel can close beats an invisible one.\n this.loadedDetails = [];\n this.mergeDetails(fetched);\n return true;\n }\n\n /**\n * Cheap single-value reads for TEMPLATE binding.\n *\n * These exist because the chat area exposes `HasMoreAbove`, `IsLoadingOlder`,\n * `PinnedTotalCount`, and `PinnedDetails` as getters bound in its template, so every\n * change-detection cycle reads all four. Routing those through {@link GetSnapshot} copied\n * the whole loaded window four times per cycle — during streaming, per token — on a\n * component whose entire purpose is to stop doing work proportional to conversation length.\n *\n * {@link PinnedDetails} deliberately returns the internal array BY REFERENCE. Every mutator\n * replaces that array rather than mutating it, so the reference is stable between real\n * changes — which is precisely what keeps the pins panel's `ngOnChanges` from firing on\n * every cycle. Callers must not mutate it.\n */\n public get HasMoreAbove(): boolean {\n return this.cursor.HasMoreAbove;\n }\n\n public get IsLoadingOlder(): boolean {\n return this.isLoadingOlder;\n }\n\n public get IsLoadingLatest(): boolean {\n return this.isLoadingLatest;\n }\n\n public get PinnedTotalCount(): number {\n return this.pinnedTotalCount;\n }\n\n public get PinnedDetails(): readonly MJConversationDetailEntity[] {\n return this.pinnedDetails;\n }\n\n /** True when the most recent load failed — an empty transcript vs. a broken one. */\n public get LoadFailed(): boolean {\n return this.loadFailed;\n }\n\n /** The branch whose path the window pages; null is the trunk. */\n public get BranchId(): string | null {\n return this.branchId;\n }\n\n /** True when a `LoadOlder` would actually do work. Drives the sentinel's observer. */\n public CanLoadOlder(): boolean {\n return this.conversationId !== null\n && this.cursor.HasMoreAbove\n && this.cursor.OldestSequence !== null\n && !this.isLoadingOlder\n && !this.isLoadingLatest;\n }\n\n /**\n * Applies a locally-known row — sent, streamed, edited. Inserts or replaces by ID and\n * re-sorts. Never queries; the caller already has the entity.\n */\n public ApplyLocalDetail(detail: MJConversationDetailEntity): void {\n this.mergeDetails([detail]);\n const newest = this.loadedDetails[this.loadedDetails.length - 1]?.Sequence ?? null;\n this.cursor = { ...this.cursor, NewestSequence: newest };\n }\n\n /** Removes a row by ID — the delete path. */\n public RemoveDetail(detailId: string): void {\n const key = NormalizeUUID(detailId);\n this.loadedDetails = this.loadedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n const pinnedBefore = this.pinnedDetails.length;\n this.pinnedDetails = this.pinnedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n if (this.pinnedDetails.length < pinnedBefore) {\n this.pinnedTotalCount = Math.max(0, this.pinnedTotalCount - 1);\n }\n }\n\n /**\n * Records how many pins exist WITHOUT loading them.\n *\n * The conversation-open path calls only this: the chip needs a number, the panel needs\n * nothing until it is opened, and hydrating every pin on open re-imports the cost this\n * windowing work exists to remove.\n */\n public SetPinnedCount(total: number): void {\n this.pinnedTotalCount = Math.max(0, total);\n }\n\n /** Replaces the pins panel's contents. Pins outside the window are expected here. */\n public SetPinnedDetails(pins: MJConversationDetailEntity[]): void {\n this.pinnedDetails = [...pins];\n // Hydration is unbounded, so once it lands the loaded set IS the whole set — any\n // drift the local pin/unpin bookkeeping accumulated is reconciled here on purpose.\n this.pinnedTotalCount = pins.length;\n }\n\n /**\n * Reflects a pin/unpin the user just performed, without re-querying.\n *\n * Newly pinned rows go to the FRONT to match the panel's newest-pin-first order, which\n * the initial `Sequence DESC` fetch also produces.\n */\n public ApplyLocalPin(detail: MJConversationDetailEntity): void {\n const key = NormalizeUUID(detail.ID);\n const wasPinned = this.pinnedDetails.some(d => NormalizeUUID(d.ID) === key);\n const without = this.pinnedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n this.pinnedDetails = detail.IsPinned ? [detail, ...without] : without;\n\n // Counted independently of the set: before the panel is opened `pinnedDetails` is\n // deliberately EMPTY, so deriving a count from its length would report zero pins on\n // a conversation that has many. `wasPinned` keeps the two in step — without it an\n // unpin of a row that was never in the set decrements a count it never entered.\n if (detail.IsPinned && !wasPinned) {\n this.pinnedTotalCount++;\n } else if (!detail.IsPinned && wasPinned) {\n this.pinnedTotalCount = Math.max(0, this.pinnedTotalCount - 1);\n }\n }\n\n /**\n * Everything the chat area needs to render, copied. Arrays are fresh so ngOnChanges fires.\n *\n * Call this from imperative paths (a load completing, a jump loop) — NOT from a template\n * getter. It copies the loaded set on every call, so binding it into change detection\n * pays that copy once per checked cycle for state that mostly has not moved. The cheap\n * single-value accessors above exist for exactly that case.\n */\n public GetSnapshot(): ConversationDetailWindowSnapshot {\n const details = [...this.loadedDetails];\n return {\n ConversationID: this.conversationId,\n Details: details,\n Cursor: { ...this.cursor },\n PinnedDetails: [...this.pinnedDetails],\n PinnedTotalCount: this.pinnedTotalCount,\n // Peripheral maps are handed out by reference — the chat area copies them into\n // its own UI-shaped maps (LazyArtifactInfo etc.) rather than mutating these.\n AgentRunsByDetailId: this.peripherals.AgentRunsByDetailId,\n UserAvatars: this.peripherals.UserAvatars,\n RatingsByDetailId: this.peripherals.RatingsByDetailId,\n ArtifactsByDetailId: this.peripherals.ArtifactsByDetailId,\n IsLoadingLatest: this.isLoadingLatest,\n IsLoadingOlder: this.isLoadingOlder,\n LoadFailed: this.loadFailed\n };\n }\n\n /**\n * Folds a newly-loaded page's peripherals into the accumulated maps.\n *\n * Later entries win on a key collision, which matters for a window refresh: a re-fetched\n * newest page should replace a row's stale agent run rather than keep the old one.\n */\n private mergePeripherals(incoming: ConversationDetailWindowPeripherals): void {\n for (const [id, run] of incoming.AgentRunsByDetailId) {\n this.peripherals.AgentRunsByDetailId.set(id, run);\n }\n for (const [id, avatar] of incoming.UserAvatars) {\n this.peripherals.UserAvatars.set(id, avatar);\n }\n for (const [id, ratings] of incoming.RatingsByDetailId) {\n this.peripherals.RatingsByDetailId.set(id, ratings);\n }\n for (const [id, artifacts] of incoming.ArtifactsByDetailId) {\n this.peripherals.ArtifactsByDetailId.set(id, artifacts);\n }\n }\n\n /**\n * Union-merges rows into the loaded set: replace-by-ID, then sort by `Sequence`.\n * Dedupe is required because session expansion can re-return rows a prior page held.\n */\n private mergeDetails(incoming: MJConversationDetailEntity[]): void {\n const byId = new Map<string, MJConversationDetailEntity>();\n for (const detail of this.loadedDetails) {\n byId.set(NormalizeUUID(detail.ID), detail);\n }\n for (const detail of incoming) {\n byId.set(NormalizeUUID(detail.ID), detail);\n }\n this.loadedDetails = [...byId.values()].sort((a, b) => a.Sequence - b.Sequence);\n }\n}\n\n/** Empty peripheral maps — the reset / no-window state. */\nfunction emptyPeripherals(): ConversationDetailWindowPeripherals {\n return {\n AgentRunsByDetailId: new Map(),\n UserAvatars: new Map(),\n RatingsByDetailId: new Map(),\n ArtifactsByDetailId: new Map()\n };\n}\n\n/** Copies a load result's peripheral maps into fresh maps the store then owns and merges into. */\nfunction peripheralsFrom(result: DetailWindowLoadResult): ConversationDetailWindowPeripherals {\n return {\n AgentRunsByDetailId: new Map(result.AgentRunsByDetailId),\n UserAvatars: new Map(result.UserAvatars),\n RatingsByDetailId: new Map(result.RatingsByDetailId),\n ArtifactsByDetailId: new Map(result.ArtifactsByDetailId)\n };\n}\n\n/** The empty-window cursor. */\nfunction emptyCursor(): ConversationDetailWindowCursor {\n return { OldestSequence: null, NewestSequence: null, HasMoreAbove: false };\n}\n\n// NOTE: there is deliberately no `cursorFrom(result)` helper. A cursor can never be copied\n// straight off a load result, because the store slices the over-read page down to `pageSize`\n// timeline items — so both the oldest bound and `HasMoreAbove` depend on what survived the\n// slice, not on what the engine fetched.\n"]}
1
+ {"version":3,"file":"conversation-detail-window.store.js","sourceRoot":"","sources":["../../../src/lib/services/conversation-detail-window.store.ts"],"names":[],"mappings":"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","sourcesContent":["import { NormalizeUUID } from '@memberjunction/global';\nimport type { UserInfo } from '@memberjunction/core';\nimport type {\n MJConversationDetailEntity,\n LoadDetailWindowParams,\n DetailWindowLoadResult\n} from '@memberjunction/core-entities';\nimport { BuildConversationTimeline } from '../utils/realtime-session-timeline';\nimport {\n ConversationDetailWindowCursor,\n SelectLatestTimelinePage,\n DEFAULT_TRANSCRIPT_PAGE_SIZE,\n DEFAULT_RAW_OVERREAD,\n MAX_OVERREAD_ATTEMPTS,\n OVERREAD_GROWTH_FACTOR,\n MAX_REFRESH_BACKFILL_PAGES\n} from '../utils/conversation-detail-window';\n\n/**\n * The engine surface this store needs. Declared structurally rather than importing\n * ConversationEngine so tests can pass a plain `{ LoadDetailWindow: vi.fn() }` with no\n * TestBed and no engine bootstrap.\n */\nexport interface DetailWindowLoader {\n LoadDetailWindow(params: LoadDetailWindowParams, contextUser: UserInfo): Promise<DetailWindowLoadResult>;\n}\n\n/**\n * Peripheral data for the loaded window, keyed the way the chat area already consumes it.\n *\n * Accumulates across pages: an older page brings its own artifacts and ratings, which merge\n * into these maps rather than replacing them.\n */\nexport type ConversationDetailWindowPeripherals = Pick<\n DetailWindowLoadResult,\n 'AgentRunsByDetailId' | 'UserAvatars' | 'RatingsByDetailId' | 'ArtifactsByDetailId'\n>;\n\n/**\n * What a tail refresh read, and whether it managed to reach back to the rows already loaded.\n *\n * `ReachedPreviousTail: false` is the \"the conversation ran away from us\" case — see\n * {@link MAX_REFRESH_BACKFILL_PAGES}. It is the only signal that the loaded set and the fresh\n * set do not join up, so it must not be dropped between the read and the merge.\n */\ninterface RefreshedTail {\n Results: DetailWindowLoadResult[];\n ReachedPreviousTail: boolean;\n /**\n * True when any read in the bridge failed.\n *\n * Kept separate from `ReachedPreviousTail: false`, which means \"the tail is genuinely\n * further away than the budget allows\" and legitimately discards the disconnected older\n * rows. A FAILED read must never trigger that — throwing away a reader's loaded history\n * because one query blipped is not a recovery, it is the bug wearing a different hat.\n */\n Failed: boolean;\n}\n\n/** What the chat area binds to after any store operation. */\nexport interface ConversationDetailWindowSnapshot extends ConversationDetailWindowPeripherals {\n ConversationID: string | null;\n /** Loaded rows, chronological by Sequence. A NEW array each call, so ngOnChanges fires. */\n Details: MJConversationDetailEntity[];\n Cursor: ConversationDetailWindowCursor;\n /** Pinned rows for the pins panel, INCLUDING pins older than the window. */\n PinnedDetails: MJConversationDetailEntity[];\n /**\n * TRUE total pins in the conversation, which is NOT `PinnedDetails.length` until the\n * panel has been opened — the open path reads only the count so the chip has a number\n * without hydrating anything.\n */\n PinnedTotalCount: number;\n IsLoadingLatest: boolean;\n IsLoadingOlder: boolean;\n /**\n * True when the most recent load FAILED, as opposed to returning nothing.\n *\n * Exists so an empty transcript can be told apart from a broken one. Without it a failed\n * first read renders as a conversation with no messages, which reads to the user as \"my\n * conversation is gone\" — the one failure mode worse than an error message.\n */\n LoadFailed: boolean;\n}\n\n/**\n * Holds ONE conversation's loaded transcript window and the cursors for paging further back.\n *\n * Owned per chat-area instance (not a singleton) so two hosts can never share window state.\n *\n * Contracts:\n * - {@link Reset} bumps a generation counter; every in-flight load checks it after each\n * await and discards its result if the conversation changed underneath.\n * - {@link LoadOlder} is a no-op while a load is in flight or when nothing is above.\n * - A window loaded with an `upToSequence` ({@link LoadLatest}) holds only rows at or below it;\n * every later read keeps that bound.\n * - Merges dedupe by normalized ID and re-sort by `Sequence` — session expansion can\n * return rows an earlier page already had.\n * - {@link ApplyLocalDetail} never hits the network; it is the send/stream/edit path.\n * - Pins live in their own array. They are NOT unioned into the transcript, because a pin\n * below the window's oldest Sequence would break contiguity and the paging cursor.\n */\nexport class ConversationDetailWindowStore {\n private loader: DetailWindowLoader;\n private conversationId: string | null = null;\n private branchId: string | null = null;\n private loadedDetails: MJConversationDetailEntity[] = [];\n private pinnedDetails: MJConversationDetailEntity[] = [];\n private pinnedTotalCount = 0;\n private peripherals: ConversationDetailWindowPeripherals = emptyPeripherals();\n private cursor: ConversationDetailWindowCursor = emptyCursor();\n private generation = 0;\n private isLoadingLatest = false;\n private isLoadingOlder = false;\n private loadFailed = false;\n /** The newest `Sequence` the window may hold; null for no bound. */\n private upToSequence: number | null = null;\n /** IDs of persisted rows that the loader returned with `Sequence === 0` (legacy rows). */\n private serverAssignedZeroSequenceIds = new Set<string>();\n\n constructor(loader: DetailWindowLoader) {\n this.loader = loader;\n }\n\n /**\n * Drops all state and invalidates in-flight loads. Call on every conversation switch.\n * `branchId` is the branch whose path every later load pages; null is the trunk.\n */\n public Reset(conversationId: string | null, branchId: string | null = null): void {\n this.generation++;\n this.conversationId = conversationId;\n this.branchId = branchId;\n this.loadedDetails = [];\n this.pinnedDetails = [];\n this.pinnedTotalCount = 0;\n this.peripherals = emptyPeripherals();\n this.cursor = emptyCursor();\n this.isLoadingLatest = false;\n this.isLoadingOlder = false;\n this.loadFailed = false;\n this.upToSequence = null;\n this.serverAssignedZeroSequenceIds.clear();\n }\n\n /**\n * The exclusive `BeforeSequence` that keeps a newest-rows read at or below {@link upToSequence};\n * undefined when the window has no bound. `Sequence` is an int column, so `< n + 1` is `<= n`.\n */\n private newestReadBound(): number | undefined {\n return this.upToSequence == null ? undefined : this.upToSequence + 1;\n }\n\n /**\n * Fetches a page, WIDENING the raw over-read until it yields a full page of timeline\n * items (or the conversation runs out).\n *\n * Raw rows are not display items: a realtime session folds many stamped rows into ONE\n * timeline card, so the default `3 × pageSize` over-read can come back holding two or\n * three items instead of ten. Taking that at face value gives the reader a stubby page\n * and makes the sentinel fire again immediately — which is worse than one wider read.\n *\n * Bounded by {@link MAX_OVERREAD_ATTEMPTS}: a conversation that is almost entirely one\n * long session would otherwise grow the read until it pulled the whole table.\n */\n private async fetchPageFillingTimeline(\n params: LoadDetailWindowParams,\n contextUser: UserInfo\n ): Promise<DetailWindowLoadResult> {\n let overread = DEFAULT_RAW_OVERREAD;\n let result = await this.loader.LoadDetailWindow({ ...params, RawOverread: overread }, contextUser);\n\n for (let attempt = 1; attempt < MAX_OVERREAD_ATTEMPTS; attempt++) {\n const itemCount = BuildConversationTimeline(result.Details).length;\n if (itemCount >= DEFAULT_TRANSCRIPT_PAGE_SIZE) {\n break; // already a full page of display items\n }\n if (result.Failed) {\n break; // widening a failed read just fails again\n }\n if (!result.HasMoreAbove) {\n break; // start of the conversation — short IS complete\n }\n // The decisive test: did the read actually HIT its row limit? A fetch that came\n // back under its own `MaxRows` has already returned everything available in that\n // range, so a wider read would return the identical rows. Only a fetch that filled\n // its limit can have been truncated by collapse, and only that is worth re-reading.\n if (result.Details.length < overread) {\n break;\n }\n\n overread *= OVERREAD_GROWTH_FACTOR;\n const wider = await this.loader.LoadDetailWindow({ ...params, RawOverread: overread }, contextUser);\n // A failed or non-productive retry must not discard the page we already have.\n if (!wider || wider.Details.length <= result.Details.length) {\n break;\n }\n result = wider;\n }\n return result;\n }\n\n /**\n * First paint: the newest page of a conversation's path.\n *\n * With `upToSequence`, the window holds only rows at or below that `Sequence`: the first page\n * ends there, and every later read of the window keeps the bound. Older pages load as usual.\n *\n * The loader is not expected to throw — `LoadDetailWindow` logs and returns an empty\n * window on a failed read — but a transport-layer rejection would otherwise strand\n * `isLoadingLatest` at true and wedge the store, so the flag is cleared in `finally`.\n */\n public async LoadLatest(\n conversationId: string,\n contextUser: UserInfo,\n branchId: string | null = null,\n upToSequence: number | null = null\n ): Promise<void> {\n this.Reset(conversationId, branchId);\n this.upToSequence = upToSequence;\n const generation = this.generation;\n this.isLoadingLatest = true;\n\n try {\n const before = this.newestReadBound();\n const result = await this.fetchPageFillingTimeline(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE,\n ...(before === undefined ? {} : { BeforeSequence: before })\n },\n contextUser\n );\n\n if (generation !== this.generation) {\n return; // user switched conversations mid-flight\n }\n // The engine OVER-READS raw rows (a page of N rows can collapse to one session\n // card), so the fetch returns more than a page's worth. Slice to the newest\n // `pageSize` TIMELINE ITEMS here — this is what actually bounds the transcript.\n const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n const droppedOlderRows = page.Page.length < result.Details.length;\n\n this.recordServerZeroSequences(page.Page);\n this.mergeDetails(page.Page);\n this.peripherals = peripheralsFrom(result);\n // A failed first read leaves the window empty. `LoadFailed` is what stops that\n // rendering as \"this conversation has no messages\" — there is no cursor to\n // protect here (an empty window cannot page), so the flag IS the whole remedy.\n this.loadFailed = result.Failed;\n this.cursor = {\n OldestSequence: page.OldestIncluded?.Sequence ?? result.OldestSequence,\n NewestSequence: result.NewestSequence,\n // Rows the slice discarded are older content that IS available — the engine's\n // probe only knows about rows below what it fetched, not below what we kept.\n HasMoreAbove: result.HasMoreAbove || droppedOlderRows\n };\n } finally {\n // Guarded: a stale load must not clear a newer load's flag.\n if (generation === this.generation) {\n this.isLoadingLatest = false;\n }\n }\n }\n\n /**\n * Prepends the next older page. No-op when nothing is above or a load is running.\n *\n * `isLoadingOlder` is cleared in `finally` for the same reason as {@link LoadLatest} —\n * and it matters more here, because a stuck flag makes {@link CanLoadOlder} false\n * forever, which silently kills the transcript's \"earlier messages\" sentinel.\n */\n public async LoadOlder(contextUser: UserInfo): Promise<void> {\n if (!this.CanLoadOlder()) {\n return;\n }\n const generation = this.generation;\n const conversationId = this.conversationId as string;\n const before = this.cursor.OldestSequence as number;\n this.isLoadingOlder = true;\n\n try {\n const result = await this.fetchPageFillingTimeline(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n BeforeSequence: before,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE\n },\n contextUser\n );\n\n if (generation !== this.generation) {\n return;\n }\n this.loadFailed = result.Failed;\n if (result.Failed) {\n // Leave the cursor EXACTLY as it was. Folding a failed read's `HasMoreAbove:\n // false` into it would retire the \"earlier messages\" sentinel permanently —\n // the template unmounts it, the observer disconnects, and no later call ever\n // sets the flag back, so one transport blip costs the reader the rest of the\n // conversation's history with nothing said. The next sentinel fire retries.\n return;\n }\n\n // Same over-read slice as LoadLatest: keep the newest page of timeline items\n // from what came back, not every raw row.\n const page = SelectLatestTimelinePage(result.Details, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n const droppedOlderRows = page.Page.length < result.Details.length;\n\n this.recordServerZeroSequences(page.Page);\n this.mergeDetails(page.Page);\n // Older pages bring their OWN artifacts/ratings/runs — merge, never replace,\n // or paging up would strip the peripherals off the rows already on screen.\n this.mergePeripherals(result);\n // Only the upward bound moves — the tail is whatever we already had.\n this.cursor = {\n OldestSequence: oldestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds) ?? this.cursor.OldestSequence,\n NewestSequence: this.cursor.NewestSequence,\n HasMoreAbove: result.HasMoreAbove || droppedOlderRows\n };\n } finally {\n if (generation === this.generation) {\n this.isLoadingOlder = false;\n }\n }\n }\n\n /**\n * Re-fetches the tail and folds it into the existing window.\n *\n * The refresh-in-place counterpart to {@link LoadLatest}: used when something changed at\n * the tail (an agent finished, artifacts were written) and the transcript needs to pick\n * it up. Unlike `LoadLatest` it does NOT `Reset`, so a user who has paged up five times\n * keeps those pages instead of being yanked back to a 10-row tail.\n *\n * Reads via {@link fetchRefreshedTail}, which keeps paging down until it reaches rows the\n * window already holds — otherwise a conversation that grew by more than a page leaves a\n * hole in the middle of the merged set that nothing in the UI can reveal.\n */\n public async RefreshLatest(contextUser: UserInfo): Promise<void> {\n const conversationId = this.conversationId;\n if (!conversationId) {\n return;\n }\n const generation = this.generation;\n const previousOldest = this.cursor.OldestSequence;\n const previousNewest = this.cursor.NewestSequence;\n this.isLoadingLatest = true;\n\n try {\n const tail = await this.fetchRefreshedTail(\n conversationId, previousNewest, generation, contextUser\n );\n if (generation !== this.generation) {\n return;\n }\n this.loadFailed = tail.Failed;\n if (tail.Failed) {\n // Apply NOTHING. A bridge only reaches a second page because the first did not\n // join, so a failure part-way through means the fresh rows cannot be merged\n // without opening the very gap this method exists to close — and the fallback\n // for an unbridgeable tail discards loaded history, which is far too\n // destructive a response to a transient read error. Leave the window as it\n // was; the next refresh retries.\n return;\n }\n this.applyRefreshedTail(tail, previousOldest, previousNewest);\n } finally {\n if (generation === this.generation) {\n this.isLoadingLatest = false;\n }\n }\n }\n\n /**\n * Reads the newest page, then keeps paging down until the read reaches back to\n * `previousNewest` — the newest row the window already holds.\n *\n * One page is enough in the normal case: a refresh usually returns rows we already have\n * plus the handful that arrived since. The loop is for the case that is not normal, where\n * the tail has moved further than a page and a single read would land entirely above the\n * loaded set.\n *\n * @returns Every page read, plus whether the reads joined up with the loaded window.\n */\n private async fetchRefreshedTail(\n conversationId: string,\n previousNewest: number | null,\n generation: number,\n contextUser: UserInfo\n ): Promise<RefreshedTail> {\n const results: DetailWindowLoadResult[] = [];\n let before: number | undefined = this.newestReadBound();\n\n for (let page = 0; page <= MAX_REFRESH_BACKFILL_PAGES; page++) {\n const result = await this.loader.LoadDetailWindow(\n {\n ConversationID: conversationId,\n BranchID: this.branchId,\n BeforeSequence: before,\n PageSize: DEFAULT_TRANSCRIPT_PAGE_SIZE\n },\n contextUser\n );\n if (generation !== this.generation) {\n return { Results: results, ReachedPreviousTail: false, Failed: false }; // discarded\n }\n // Checked BEFORE tailIsJoined, which reads `HasMoreAbove` — false on a failed read,\n // which would report the bridge as joined and hand back a set with a hole in it.\n if (result.Failed) {\n return { Results: results, ReachedPreviousTail: false, Failed: true };\n }\n results.push(result);\n\n if (this.tailIsJoined(result, previousNewest)) {\n return { Results: results, ReachedPreviousTail: true, Failed: false };\n }\n before = result.OldestSequence ?? undefined;\n }\n return { Results: results, ReachedPreviousTail: false, Failed: false };\n }\n\n /** True when this page reaches back into the loaded window, so nothing sits between them. */\n private tailIsJoined(result: DetailWindowLoadResult, previousNewest: number | null): boolean {\n // Nothing loaded to join up WITH, or the read came back empty — either way, done.\n if (previousNewest === null || result.OldestSequence === null) {\n return true;\n }\n // The page overlaps or abuts what we hold.\n if (result.OldestSequence <= previousNewest) {\n return true;\n }\n // The engine's probe says no row exists below this page at all. The rows we hold are\n // below it, so they have since been deleted — there is no gap left to bridge.\n return !result.HasMoreAbove;\n }\n\n /**\n * Folds a refreshed tail into the window, choosing what to keep by whether the read\n * joined up with the rows already loaded.\n */\n private applyRefreshedTail(\n tail: RefreshedTail,\n previousOldest: number | null,\n previousNewest: number | null\n ): void {\n const fetched = tail.Results.flatMap(result => result.Details);\n this.recordServerZeroSequences(fetched);\n const hasMoreAbove = this.mergeRefreshedRows(tail, fetched, previousOldest, previousNewest);\n\n for (const result of tail.Results) {\n this.mergePeripherals(result);\n }\n this.cursor = {\n OldestSequence: oldestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds),\n NewestSequence: newestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds),\n HasMoreAbove: hasMoreAbove\n };\n }\n\n /** Merges the refreshed rows per the three refresh cases. @returns the new `HasMoreAbove`. */\n private mergeRefreshedRows(\n tail: RefreshedTail,\n fetched: MJConversationDetailEntity[],\n previousOldest: number | null,\n previousNewest: number | null\n ): boolean {\n if (previousNewest === null) {\n // Nothing loaded — this is a first paint wearing a refresh's name, so bound it to\n // a page the way LoadLatest does.\n const page = SelectLatestTimelinePage(fetched, DEFAULT_TRANSCRIPT_PAGE_SIZE);\n this.mergeDetails(page.Page);\n const last = tail.Results[tail.Results.length - 1];\n return (last?.HasMoreAbove ?? false) || page.Page.length < fetched.length;\n }\n\n if (tail.ReachedPreviousTail) {\n // Keep EVERY fetched row down to the window's existing floor. Slicing to a page\n // here is what opened the hole: the read deliberately spans the distance back to\n // the loaded tail, and discarding its middle throws away the very rows that make\n // the merged set contiguous. Rows below the floor are dropped so a refresh cannot\n // quietly grow the window downward — that is LoadOlder's job.\n this.mergeDetails(previousOldest === null\n ? fetched\n : fetched.filter(detail => detail.Sequence >= previousOldest));\n // The floor did not move, so what is below it is still whatever we last learned.\n return this.cursor.HasMoreAbove;\n }\n\n // Could not bridge within the page budget. Drop the now-disconnected older rows and\n // keep the fresh tail: a visible gap the sentinel can close beats an invisible one.\n this.loadedDetails = [];\n this.mergeDetails(fetched);\n return true;\n }\n\n /**\n * Cheap single-value reads for TEMPLATE binding.\n *\n * These exist because the chat area exposes `HasMoreAbove`, `IsLoadingOlder`,\n * `PinnedTotalCount`, and `PinnedDetails` as getters bound in its template, so every\n * change-detection cycle reads all four. Routing those through {@link GetSnapshot} copied\n * the whole loaded window four times per cycle — during streaming, per token — on a\n * component whose entire purpose is to stop doing work proportional to conversation length.\n *\n * {@link PinnedDetails} deliberately returns the internal array BY REFERENCE. Every mutator\n * replaces that array rather than mutating it, so the reference is stable between real\n * changes — which is precisely what keeps the pins panel's `ngOnChanges` from firing on\n * every cycle. Callers must not mutate it.\n */\n public get HasMoreAbove(): boolean {\n return this.cursor.HasMoreAbove;\n }\n\n public get IsLoadingOlder(): boolean {\n return this.isLoadingOlder;\n }\n\n public get IsLoadingLatest(): boolean {\n return this.isLoadingLatest;\n }\n\n public get PinnedTotalCount(): number {\n return this.pinnedTotalCount;\n }\n\n public get PinnedDetails(): readonly MJConversationDetailEntity[] {\n return this.pinnedDetails;\n }\n\n /** True when the most recent load failed — an empty transcript vs. a broken one. */\n public get LoadFailed(): boolean {\n return this.loadFailed;\n }\n\n /** The branch whose path the window pages; null is the trunk. */\n public get BranchId(): string | null {\n return this.branchId;\n }\n\n /** True when a `LoadOlder` would actually do work. Drives the sentinel's observer. */\n public CanLoadOlder(): boolean {\n return this.conversationId !== null\n && this.cursor.HasMoreAbove\n && this.cursor.OldestSequence !== null\n && !this.isLoadingOlder\n && !this.isLoadingLatest;\n }\n\n /**\n * Applies a locally-known row — sent, streamed, edited. Inserts or replaces by ID and\n * re-sorts. Never queries; the caller already has the entity.\n */\n public ApplyLocalDetail(detail: MJConversationDetailEntity): void {\n this.mergeDetails([detail]);\n const newest = newestAssignedSequence(this.loadedDetails, this.serverAssignedZeroSequenceIds) ?? this.cursor.NewestSequence;\n this.cursor = { ...this.cursor, NewestSequence: newest };\n }\n\n /** Removes a row by ID — the delete path. */\n public RemoveDetail(detailId: string): void {\n const key = NormalizeUUID(detailId);\n this.loadedDetails = this.loadedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n const pinnedBefore = this.pinnedDetails.length;\n this.pinnedDetails = this.pinnedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n if (this.pinnedDetails.length < pinnedBefore) {\n this.pinnedTotalCount = Math.max(0, this.pinnedTotalCount - 1);\n }\n }\n\n /**\n * Records how many pins exist WITHOUT loading them.\n *\n * The conversation-open path calls only this: the chip needs a number, the panel needs\n * nothing until it is opened, and hydrating every pin on open re-imports the cost this\n * windowing work exists to remove.\n */\n public SetPinnedCount(total: number): void {\n this.pinnedTotalCount = Math.max(0, total);\n }\n\n /** Replaces the pins panel's contents. Pins outside the window are expected here. */\n public SetPinnedDetails(pins: MJConversationDetailEntity[]): void {\n this.pinnedDetails = [...pins];\n // Hydration is unbounded, so once it lands the loaded set IS the whole set — any\n // drift the local pin/unpin bookkeeping accumulated is reconciled here on purpose.\n this.pinnedTotalCount = pins.length;\n }\n\n /**\n * Reflects a pin/unpin the user just performed, without re-querying.\n *\n * Newly pinned rows go to the FRONT to match the panel's newest-pin-first order, which\n * the initial `Sequence DESC` fetch also produces.\n */\n public ApplyLocalPin(detail: MJConversationDetailEntity): void {\n const key = NormalizeUUID(detail.ID);\n const wasPinned = this.pinnedDetails.some(d => NormalizeUUID(d.ID) === key);\n const without = this.pinnedDetails.filter(d => NormalizeUUID(d.ID) !== key);\n this.pinnedDetails = detail.IsPinned ? [detail, ...without] : without;\n\n // Counted independently of the set: before the panel is opened `pinnedDetails` is\n // deliberately EMPTY, so deriving a count from its length would report zero pins on\n // a conversation that has many. `wasPinned` keeps the two in step — without it an\n // unpin of a row that was never in the set decrements a count it never entered.\n if (detail.IsPinned && !wasPinned) {\n this.pinnedTotalCount++;\n } else if (!detail.IsPinned && wasPinned) {\n this.pinnedTotalCount = Math.max(0, this.pinnedTotalCount - 1);\n }\n }\n\n /**\n * Everything the chat area needs to render, copied. Arrays are fresh so ngOnChanges fires.\n *\n * Call this from imperative paths (a load completing, a jump loop) — NOT from a template\n * getter. It copies the loaded set on every call, so binding it into change detection\n * pays that copy once per checked cycle for state that mostly has not moved. The cheap\n * single-value accessors above exist for exactly that case.\n */\n public GetSnapshot(): ConversationDetailWindowSnapshot {\n const details = [...this.loadedDetails];\n return {\n ConversationID: this.conversationId,\n Details: details,\n Cursor: { ...this.cursor },\n PinnedDetails: [...this.pinnedDetails],\n PinnedTotalCount: this.pinnedTotalCount,\n // Peripheral maps are handed out by reference — the chat area copies them into\n // its own UI-shaped maps (LazyArtifactInfo etc.) rather than mutating these.\n AgentRunsByDetailId: this.peripherals.AgentRunsByDetailId,\n UserAvatars: this.peripherals.UserAvatars,\n RatingsByDetailId: this.peripherals.RatingsByDetailId,\n ArtifactsByDetailId: this.peripherals.ArtifactsByDetailId,\n IsLoadingLatest: this.isLoadingLatest,\n IsLoadingOlder: this.isLoadingOlder,\n LoadFailed: this.loadFailed\n };\n }\n\n /**\n * Folds a newly-loaded page's peripherals into the accumulated maps.\n *\n * Later entries win on a key collision, which matters for a window refresh: a re-fetched\n * newest page should replace a row's stale agent run rather than keep the old one.\n */\n private mergePeripherals(incoming: ConversationDetailWindowPeripherals): void {\n for (const [id, run] of incoming.AgentRunsByDetailId) {\n this.peripherals.AgentRunsByDetailId.set(id, run);\n }\n for (const [id, avatar] of incoming.UserAvatars) {\n this.peripherals.UserAvatars.set(id, avatar);\n }\n for (const [id, ratings] of incoming.RatingsByDetailId) {\n this.peripherals.RatingsByDetailId.set(id, ratings);\n }\n for (const [id, artifacts] of incoming.ArtifactsByDetailId) {\n this.peripherals.ArtifactsByDetailId.set(id, artifacts);\n }\n }\n\n private recordServerZeroSequences(rows: readonly MJConversationDetailEntity[]): void {\n for (const row of rows) {\n if (row.Sequence === 0) {\n this.serverAssignedZeroSequenceIds.add(NormalizeUUID(row.ID));\n }\n }\n }\n\n /**\n * Union-merges rows into the loaded set: replace-by-ID, then sort by `Sequence`.\n * Dedupe is required because session expansion can re-return rows a prior page held.\n * Unsaved local placeholders (`Sequence === 0`) have no sequence yet and stay at the tail\n * in insertion order instead of sorting ahead of `Sequence >= 1` rows.\n */\n private mergeDetails(incoming: MJConversationDetailEntity[]): void {\n const byId = new Map<string, MJConversationDetailEntity>();\n for (const detail of this.loadedDetails) {\n byId.set(NormalizeUUID(detail.ID), detail);\n }\n for (const detail of incoming) {\n byId.set(NormalizeUUID(detail.ID), detail);\n }\n this.loadedDetails = [...byId.values()].sort((a, b) => {\n const aAssigned = hasAssignedSequence(a, this.serverAssignedZeroSequenceIds);\n const bAssigned = hasAssignedSequence(b, this.serverAssignedZeroSequenceIds);\n if (aAssigned && bAssigned) {\n return a.Sequence - b.Sequence;\n }\n if (aAssigned !== bAssigned) {\n return aAssigned ? -1 : 1;\n }\n return 0;\n });\n }\n}\n\n/** True when a row has a persisted `Sequence` (unsaved local placeholders have `Sequence === 0` and are not in `serverZeroIds`). */\nfunction hasAssignedSequence(detail: MJConversationDetailEntity, serverZeroIds: ReadonlySet<string>): boolean {\n return typeof detail.Sequence === 'number' && (\n detail.Sequence > 0 || (detail.Sequence === 0 && serverZeroIds.has(NormalizeUUID(detail.ID)))\n );\n}\n\n/** The smallest assigned `Sequence` in a sorted loaded-details slice, or `null` when none is assigned. */\nfunction oldestAssignedSequence(details: readonly MJConversationDetailEntity[], serverZeroIds: ReadonlySet<string>): number | null {\n for (let i = 0; i < details.length; i++) {\n if (hasAssignedSequence(details[i], serverZeroIds)) {\n return details[i].Sequence;\n }\n }\n return null;\n}\n\n/** The largest assigned `Sequence` in a sorted loaded-details slice, or `null` when none is assigned. */\nfunction newestAssignedSequence(details: readonly MJConversationDetailEntity[], serverZeroIds: ReadonlySet<string>): number | null {\n for (let i = details.length - 1; i >= 0; i--) {\n if (hasAssignedSequence(details[i], serverZeroIds)) {\n return details[i].Sequence;\n }\n }\n return null;\n}\n\n/** Empty peripheral maps — the reset / no-window state. */\nfunction emptyPeripherals(): ConversationDetailWindowPeripherals {\n return {\n AgentRunsByDetailId: new Map(),\n UserAvatars: new Map(),\n RatingsByDetailId: new Map(),\n ArtifactsByDetailId: new Map()\n };\n}\n\n/** Copies a load result's peripheral maps into fresh maps the store then owns and merges into. */\nfunction peripheralsFrom(result: DetailWindowLoadResult): ConversationDetailWindowPeripherals {\n return {\n AgentRunsByDetailId: new Map(result.AgentRunsByDetailId),\n UserAvatars: new Map(result.UserAvatars),\n RatingsByDetailId: new Map(result.RatingsByDetailId),\n ArtifactsByDetailId: new Map(result.ArtifactsByDetailId)\n };\n}\n\n/** The empty-window cursor. */\nfunction emptyCursor(): ConversationDetailWindowCursor {\n return { OldestSequence: null, NewestSequence: null, HasMoreAbove: false };\n}\n\n// NOTE: there is deliberately no `cursorFrom(result)` helper. A cursor can never be copied\n// straight off a load result, because the store slices the over-read page down to `pageSize`\n// timeline items — so both the oldest bound and `HasMoreAbove` depend on what survived the\n// slice, not on what the engine fetched.\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-session.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;;AA4B1E;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,sBAAuB,SAAQ,sBAAsB;;IAQ9D;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,6BAA6B,CAAC,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,GAAG,IAAI;yCA1BlG,sBAAsB;6CAAtB,sBAAsB;CAoClC;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAEhC,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClE;AAED,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IAElC,QAAQ,CAAC,eAAe,EAAE,uBAAuB,CAAC;IAElD,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC1E"}
1
+ {"version":3,"file":"realtime-session.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;;AA8B1E;;;;;;;;;;;;;;;;GAgBG;AACH,qBACa,sBAAuB,SAAQ,sBAAsB;;IAQ9D;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,6BAA6B,CAAC,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,qBAAqB,GAAG,IAAI;yCA1BlG,sBAAsB;6CAAtB,sBAAsB;CAoClC;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAEhC,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClE;AAED,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IAElC,QAAQ,CAAC,eAAe,EAAE,uBAAuB,CAAC;IAElD,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC1E"}
@@ -1,6 +1,6 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import { RealtimeSessionRuntime } from '@memberjunction/realtime-runtime';
3
- import { LoadAssemblyAIRealtimeClient, LoadElevenLabsRealtimeClient, LoadGeminiRealtimeClient, LoadHuggingFaceRealtimeClient, LoadOpenAILiveClient, LoadOpenAIRealtimeClient, LoadxAIRealtimeClient, } from '@memberjunction/ai-realtime-client';
3
+ import { LoadAssemblyAIRealtimeClient, LoadElevenLabsRealtimeClient, LoadElevenRealtimeClient, LoadGeminiRealtimeClient, LoadHuggingFaceRealtimeClient, LoadOpenAILiveClient, LoadOpenAIRealtimeClient, LoadxAIRealtimeClient, } from '@memberjunction/ai-realtime-client';
4
4
  import { BrowserRealtimeMediaHost } from './browser-realtime-media-host';
5
5
  import * as i0 from "@angular/core";
6
6
  // Tree-shaking prevention: client drivers are resolved dynamically through the ClassFactory (by
@@ -17,6 +17,7 @@ LoadGeminiRealtimeClient();
17
17
  LoadElevenLabsRealtimeClient();
18
18
  LoadAssemblyAIRealtimeClient();
19
19
  LoadxAIRealtimeClient();
20
+ LoadElevenRealtimeClient();
20
21
  LoadHuggingFaceRealtimeClient();
21
22
  /**
22
23
  * Angular host for MemberJunction's realtime co-agent sessions.
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-session.service.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EACH,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;;AAEzE,gGAAgG;AAChG,mGAAmG;AACnG,gDAAgD;AAChD,EAAE;AACF,+FAA+F;AAC/F,0FAA0F;AAC1F,+FAA+F;AAC/F,kBAAkB;AAClB,wBAAwB,EAAE,CAAC;AAC3B,oBAAoB,EAAE,CAAC;AACvB,wBAAwB,EAAE,CAAC;AAC3B,4BAA4B,EAAE,CAAC;AAC/B,4BAA4B,EAAE,CAAC;AAC/B,qBAAqB,EAAE,CAAC;AACxB,6BAA6B,EAAE,CAAC;AAEhC;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,OAAO,sBAAuB,SAAQ,sBAAsB;IAC9D;QACI,KAAK,CAAC,IAAI,wBAAwB,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACnE,IAAI,CAAC,6BAA6B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACzD,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACO,6BAA6B,CAAC,IAAyB,EAAE,UAAiC;QAChG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACrF,UAAU,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,UAAU,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;gBAC1C,sFAAsF;gBACtF,sFAAsF;gBACtF,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;gHAnCQ,sBAAsB;gEAAtB,sBAAsB,WAAtB,sBAAsB,mBADT,MAAM;;iFACnB,sBAAsB;cADlC,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable } from '@angular/core';\nimport { RealtimeSessionRuntime } from '@memberjunction/realtime-runtime';\nimport {\n LoadAssemblyAIRealtimeClient,\n LoadElevenLabsRealtimeClient,\n LoadGeminiRealtimeClient,\n LoadHuggingFaceRealtimeClient,\n LoadOpenAILiveClient,\n LoadOpenAIRealtimeClient,\n LoadxAIRealtimeClient,\n} from '@memberjunction/ai-realtime-client';\nimport { BrowserRealtimeMediaHost } from './browser-realtime-media-host';\n\n// Tree-shaking prevention: client drivers are resolved dynamically through the ClassFactory (by\n// the server-reported Provider key), so these static calls are what keep their @RegisterClass side\n// effects from being eliminated by the bundler.\n//\n// NOTE: the interactive-channel plugins (resolved dynamically from the `MJ: AI Agent Channels`\n// registry by ClientPluginClass key) get the same treatment, but their Load calls live in\n// `conversations.module.ts` — plugins carry Angular surface COMPONENTS, and this service stays\n// component-free.\nLoadOpenAIRealtimeClient();\nLoadOpenAILiveClient();\nLoadGeminiRealtimeClient();\nLoadElevenLabsRealtimeClient();\nLoadAssemblyAIRealtimeClient();\nLoadxAIRealtimeClient();\nLoadHuggingFaceRealtimeClient();\n\n/**\n * Angular host for MemberJunction's realtime co-agent sessions.\n *\n * All orchestration — mint, driver resolution, transcripts, tool relay, delegation narration,\n * channel lifecycle, usage relay, teardown — lives in\n * {@link RealtimeSessionRuntime} (`@memberjunction/realtime-runtime`), which is framework-agnostic\n * and shared with every other host, including the React Native app. This class supplies the three\n * things that are genuinely Angular/browser: the `@Injectable` shell that lets components inject\n * it, the browser's media host, and the page-lifecycle wiring that sends the recording's terminal\n * request when the tab is hidden or unloaded (#5294).\n *\n * Consumers are unaffected by that split — the injectable token, class name, and public surface\n * are unchanged. Types that used to be declared in this file (`RealtimeCaption`,\n * `RealtimeConnectionState`, `RealtimeSessionRunOptions`, …) now come from\n * `@memberjunction/realtime-runtime`; import them from there directly, since MJ does not\n * re-export across package boundaries.\n */\n@Injectable({ providedIn: 'root' })\nexport class RealtimeSessionService extends RealtimeSessionRuntime {\n constructor() {\n super(new BrowserRealtimeMediaHost());\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n this.WireRecordingTerminalTriggers(window, document);\n }\n }\n\n /**\n * Sends the recording terminal (tail shard + counts, as a keepalive request) when the page is\n * going away: on `pagehide`, and when the page becomes hidden — the last event a mobile browser\n * reliably fires before it may discard a backgrounded tab without any unload event. Both handlers\n * only call the runtime's synchronous {@link RealtimeSessionRuntime.SendRecordingTerminal}, which\n * does no IndexedDB work and no encoding: there is no time for either while a page is unloading.\n * It does nothing when no call is recording, and repeating it is safe (a sent tail is sealed, so a\n * call that continues after the tab comes back sends its later shards at new indexes).\n *\n * There is deliberately no `beforeunload` prompt any more: shards already on the server plus the\n * terminal make closing the tab safe, and a `beforeunload` listener blocks the back/forward cache.\n *\n * The service is a root singleton, so these listeners live exactly as long as the app and are\n * never removed.\n *\n * @param page the page-lifecycle event target (`window`)\n * @param visibility the visibility source (`document`)\n */\n protected WireRecordingTerminalTriggers(page: PageHideEventTarget, visibility: VisibilityEventSource): void {\n page.addEventListener('pagehide', () => this.SendRecordingTerminal({ Final: true }));\n visibility.addEventListener('visibilitychange', () => {\n if (visibility.visibilityState === 'hidden') {\n // Not final: the user may come back and the call go on, so the server only stamps the\n // counts and assembles after its grace period instead of rewriting the recording now.\n this.SendRecordingTerminal({ Final: false });\n }\n });\n }\n}\n\n/** The slice of `window` the terminal wiring needs; `window` itself satisfies it. */\nexport interface PageHideEventTarget {\n // case-violation-ok-legacy-back-compat: mirrors the DOM EventTarget API so `window` itself satisfies this interface\n addEventListener(type: 'pagehide', listener: () => void): void;\n}\n\n/** The slice of `document` the terminal wiring needs; `document` itself satisfies it. */\nexport interface VisibilityEventSource {\n // case-violation-ok-legacy-back-compat: mirrors the DOM Document API so `document` itself satisfies this interface\n readonly visibilityState: DocumentVisibilityState;\n // case-violation-ok-legacy-back-compat: mirrors the DOM EventTarget API so `document` itself satisfies this interface\n addEventListener(type: 'visibilitychange', listener: () => void): void;\n}\n"]}
1
+ {"version":3,"file":"realtime-session.service.js","sourceRoot":"","sources":["../../../src/lib/services/realtime-session.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EACH,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,EACxB,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;;AAEzE,gGAAgG;AAChG,mGAAmG;AACnG,gDAAgD;AAChD,EAAE;AACF,+FAA+F;AAC/F,0FAA0F;AAC1F,+FAA+F;AAC/F,kBAAkB;AAClB,wBAAwB,EAAE,CAAC;AAC3B,oBAAoB,EAAE,CAAC;AACvB,wBAAwB,EAAE,CAAC;AAC3B,4BAA4B,EAAE,CAAC;AAC/B,4BAA4B,EAAE,CAAC;AAC/B,qBAAqB,EAAE,CAAC;AACxB,wBAAwB,EAAE,CAAC;AAC3B,6BAA6B,EAAE,CAAC;AAEhC;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,OAAO,sBAAuB,SAAQ,sBAAsB;IAC9D;QACI,KAAK,CAAC,IAAI,wBAAwB,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACnE,IAAI,CAAC,6BAA6B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACzD,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACO,6BAA6B,CAAC,IAAyB,EAAE,UAAiC;QAChG,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACrF,UAAU,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,UAAU,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;gBAC1C,sFAAsF;gBACtF,sFAAsF;gBACtF,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;gHAnCQ,sBAAsB;gEAAtB,sBAAsB,WAAtB,sBAAsB,mBADT,MAAM;;iFACnB,sBAAsB;cADlC,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable } from '@angular/core';\nimport { RealtimeSessionRuntime } from '@memberjunction/realtime-runtime';\nimport {\n LoadAssemblyAIRealtimeClient,\n LoadElevenLabsRealtimeClient,\n LoadElevenRealtimeClient,\n LoadGeminiRealtimeClient,\n LoadHuggingFaceRealtimeClient,\n LoadOpenAILiveClient,\n LoadOpenAIRealtimeClient,\n LoadxAIRealtimeClient,\n} from '@memberjunction/ai-realtime-client';\nimport { BrowserRealtimeMediaHost } from './browser-realtime-media-host';\n\n// Tree-shaking prevention: client drivers are resolved dynamically through the ClassFactory (by\n// the server-reported Provider key), so these static calls are what keep their @RegisterClass side\n// effects from being eliminated by the bundler.\n//\n// NOTE: the interactive-channel plugins (resolved dynamically from the `MJ: AI Agent Channels`\n// registry by ClientPluginClass key) get the same treatment, but their Load calls live in\n// `conversations.module.ts` — plugins carry Angular surface COMPONENTS, and this service stays\n// component-free.\nLoadOpenAIRealtimeClient();\nLoadOpenAILiveClient();\nLoadGeminiRealtimeClient();\nLoadElevenLabsRealtimeClient();\nLoadAssemblyAIRealtimeClient();\nLoadxAIRealtimeClient();\nLoadElevenRealtimeClient();\nLoadHuggingFaceRealtimeClient();\n\n/**\n * Angular host for MemberJunction's realtime co-agent sessions.\n *\n * All orchestration — mint, driver resolution, transcripts, tool relay, delegation narration,\n * channel lifecycle, usage relay, teardown — lives in\n * {@link RealtimeSessionRuntime} (`@memberjunction/realtime-runtime`), which is framework-agnostic\n * and shared with every other host, including the React Native app. This class supplies the three\n * things that are genuinely Angular/browser: the `@Injectable` shell that lets components inject\n * it, the browser's media host, and the page-lifecycle wiring that sends the recording's terminal\n * request when the tab is hidden or unloaded (#5294).\n *\n * Consumers are unaffected by that split — the injectable token, class name, and public surface\n * are unchanged. Types that used to be declared in this file (`RealtimeCaption`,\n * `RealtimeConnectionState`, `RealtimeSessionRunOptions`, …) now come from\n * `@memberjunction/realtime-runtime`; import them from there directly, since MJ does not\n * re-export across package boundaries.\n */\n@Injectable({ providedIn: 'root' })\nexport class RealtimeSessionService extends RealtimeSessionRuntime {\n constructor() {\n super(new BrowserRealtimeMediaHost());\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n this.WireRecordingTerminalTriggers(window, document);\n }\n }\n\n /**\n * Sends the recording terminal (tail shard + counts, as a keepalive request) when the page is\n * going away: on `pagehide`, and when the page becomes hidden — the last event a mobile browser\n * reliably fires before it may discard a backgrounded tab without any unload event. Both handlers\n * only call the runtime's synchronous {@link RealtimeSessionRuntime.SendRecordingTerminal}, which\n * does no IndexedDB work and no encoding: there is no time for either while a page is unloading.\n * It does nothing when no call is recording, and repeating it is safe (a sent tail is sealed, so a\n * call that continues after the tab comes back sends its later shards at new indexes).\n *\n * There is deliberately no `beforeunload` prompt any more: shards already on the server plus the\n * terminal make closing the tab safe, and a `beforeunload` listener blocks the back/forward cache.\n *\n * The service is a root singleton, so these listeners live exactly as long as the app and are\n * never removed.\n *\n * @param page the page-lifecycle event target (`window`)\n * @param visibility the visibility source (`document`)\n */\n protected WireRecordingTerminalTriggers(page: PageHideEventTarget, visibility: VisibilityEventSource): void {\n page.addEventListener('pagehide', () => this.SendRecordingTerminal({ Final: true }));\n visibility.addEventListener('visibilitychange', () => {\n if (visibility.visibilityState === 'hidden') {\n // Not final: the user may come back and the call go on, so the server only stamps the\n // counts and assembles after its grace period instead of rewriting the recording now.\n this.SendRecordingTerminal({ Final: false });\n }\n });\n }\n}\n\n/** The slice of `window` the terminal wiring needs; `window` itself satisfies it. */\nexport interface PageHideEventTarget {\n // case-violation-ok-legacy-back-compat: mirrors the DOM EventTarget API so `window` itself satisfies this interface\n addEventListener(type: 'pagehide', listener: () => void): void;\n}\n\n/** The slice of `document` the terminal wiring needs; `document` itself satisfies it. */\nexport interface VisibilityEventSource {\n // case-violation-ok-legacy-back-compat: mirrors the DOM Document API so `document` itself satisfies this interface\n readonly visibilityState: DocumentVisibilityState;\n // case-violation-ok-legacy-back-compat: mirrors the DOM EventTarget API so `document` itself satisfies this interface\n addEventListener(type: 'visibilitychange', listener: () => void): void;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * How the chat shows a failed agent reply.
3
+ *
4
+ * The server writes a failed reply's row (only the server writes agent replies) with the run's own error text. Before
5
+ * that, the chat saved its own formatted text over it — `❌ **{agent}** failed`, a blank line, then the error — so it
6
+ * now applies that format when it renders a failed reply, and what people see does not change. Text already in that
7
+ * shape, or any other text that already leads with ❌ (rows the chat wrote before, the workflow status line, a failure
8
+ * it shows locally), is shown as it is.
9
+ *
10
+ * @param text The reply's stored text.
11
+ * @param agentName The name the chat shows for the reply's agent.
12
+ */
13
+ export declare function FormatAgentFailureForDisplay(text: string, agentName: string): string;
14
+ //# sourceMappingURL=agent-failure-display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-failure-display.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/agent-failure-display.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAYpF"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * How the chat shows a failed agent reply.
3
+ *
4
+ * The server writes a failed reply's row (only the server writes agent replies) with the run's own error text. Before
5
+ * that, the chat saved its own formatted text over it — `❌ **{agent}** failed`, a blank line, then the error — so it
6
+ * now applies that format when it renders a failed reply, and what people see does not change. Text already in that
7
+ * shape, or any other text that already leads with ❌ (rows the chat wrote before, the workflow status line, a failure
8
+ * it shows locally), is shown as it is.
9
+ *
10
+ * @param text The reply's stored text.
11
+ * @param agentName The name the chat shows for the reply's agent.
12
+ */
13
+ export function FormatAgentFailureForDisplay(text, agentName) {
14
+ // '❌ Failed' is the runner's text when the run reported no error of its own: it adds nothing to the header.
15
+ if (text.trim() === '❌ Failed') {
16
+ return `❌ **${agentName}** failed`;
17
+ }
18
+ // Any other text that already leads with ❌ says it failed itself: the workflow status line, the chat's own
19
+ // failure texts, and rows the chat wrote before.
20
+ if (text.trimStart().startsWith('❌')) {
21
+ return text;
22
+ }
23
+ const detail = text.trim();
24
+ return detail ? `❌ **${agentName}** failed\n\n${detail}` : `❌ **${agentName}** failed`;
25
+ }
26
+ //# sourceMappingURL=agent-failure-display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-failure-display.js","sourceRoot":"","sources":["../../../src/lib/utils/agent-failure-display.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAY,EAAE,SAAiB;IAC1E,4GAA4G;IAC5G,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,UAAU,EAAE,CAAC;QAC/B,OAAO,OAAO,SAAS,WAAW,CAAC;IACrC,CAAC;IACD,2GAA2G;IAC3G,iDAAiD;IACjD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO,SAAS,gBAAgB,MAAM,EAAE,CAAC,CAAC,CAAC,OAAO,SAAS,WAAW,CAAC;AACzF,CAAC","sourcesContent":["/**\n * How the chat shows a failed agent reply.\n *\n * The server writes a failed reply's row (only the server writes agent replies) with the run's own error text. Before\n * that, the chat saved its own formatted text over it — `❌ **{agent}** failed`, a blank line, then the error — so it\n * now applies that format when it renders a failed reply, and what people see does not change. Text already in that\n * shape, or any other text that already leads with ❌ (rows the chat wrote before, the workflow status line, a failure\n * it shows locally), is shown as it is.\n *\n * @param text The reply's stored text.\n * @param agentName The name the chat shows for the reply's agent.\n */\nexport function FormatAgentFailureForDisplay(text: string, agentName: string): string {\n // '❌ Failed' is the runner's text when the run reported no error of its own: it adds nothing to the header.\n if (text.trim() === '❌ Failed') {\n return `❌ **${agentName}** failed`;\n }\n // Any other text that already leads with ❌ says it failed itself: the workflow status line, the chat's own\n // failure texts, and rows the chat wrote before.\n if (text.trimStart().startsWith('❌')) {\n return text;\n }\n const detail = text.trim();\n return detail ? `❌ **${agentName}** failed\\n\\n${detail}` : `❌ **${agentName}** failed`;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { MJConversationDetailEntity } from '@memberjunction/core-entities';
2
+ /** Oldest first. A message with no creation time yet (an unsaved placeholder) is the newest, not the oldest. */
3
+ export declare function CompareMessagesByCreatedAt(a: MJConversationDetailEntity, b: MJConversationDetailEntity): number;
4
+ /**
5
+ * The list with `message` in place of the first message that has its ID, and every other copy of that ID dropped (a
6
+ * placeholder that just took the ID of a server row the list already holds), or with `message` appended when none has
7
+ * it. Returns whether it was already there.
8
+ */
9
+ export declare function UpsertMessageById(messages: MJConversationDetailEntity[], message: MJConversationDetailEntity): {
10
+ Messages: MJConversationDetailEntity[];
11
+ Existed: boolean;
12
+ };
13
+ //# sourceMappingURL=message-list-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-list-order.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/message-list-order.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF,gHAAgH;AAChH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,0BAA0B,EAAE,CAAC,EAAE,0BAA0B,GAAG,MAAM,CAE/G;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,0BAA0B,EAAE,EACtC,OAAO,EAAE,0BAA0B,GAClC;IAAE,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAS9D"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Keeping the chat's message list in order while agent replies start as unsaved placeholders.
3
+ *
4
+ * Only the server writes an agent's reply (A19): the chat shows an unsaved placeholder until the server acknowledges
5
+ * the run, then gives the placeholder the server row's ID. Until then it has no creation time, and a refresh of the
6
+ * conversation can already have brought the server's copy of the same row into the list.
7
+ */
8
+ import { UUIDsEqual } from '@memberjunction/global';
9
+ /** Oldest first. A message with no creation time yet (an unsaved placeholder) is the newest, not the oldest. */
10
+ export function CompareMessagesByCreatedAt(a, b) {
11
+ return createdAtOf(a) - createdAtOf(b);
12
+ }
13
+ /**
14
+ * The list with `message` in place of the first message that has its ID, and every other copy of that ID dropped (a
15
+ * placeholder that just took the ID of a server row the list already holds), or with `message` appended when none has
16
+ * it. Returns whether it was already there.
17
+ */
18
+ export function UpsertMessageById(messages, message) {
19
+ const index = messages.findIndex((m) => UUIDsEqual(m.ID, message.ID));
20
+ if (index < 0) {
21
+ return { Messages: [...messages, message], Existed: false };
22
+ }
23
+ const updated = messages
24
+ .map((m, i) => (i === index ? message : m))
25
+ .filter((m, i) => i === index || !UUIDsEqual(m.ID, message.ID));
26
+ return { Messages: updated, Existed: true };
27
+ }
28
+ function createdAtOf(message) {
29
+ return message.__mj_CreatedAt?.getTime() ?? Number.MAX_SAFE_INTEGER;
30
+ }
31
+ //# sourceMappingURL=message-list-order.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-list-order.js","sourceRoot":"","sources":["../../../src/lib/utils/message-list-order.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,gHAAgH;AAChH,MAAM,UAAU,0BAA0B,CAAC,CAA6B,EAAE,CAA6B;IACrG,OAAO,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAsC,EACtC,OAAmC;IAEnC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9D,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,WAAW,CAAC,OAAmC;IACtD,OAAO,OAAO,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,MAAM,CAAC,gBAAgB,CAAC;AACtE,CAAC","sourcesContent":["/**\n * Keeping the chat's message list in order while agent replies start as unsaved placeholders.\n *\n * Only the server writes an agent's reply (A19): the chat shows an unsaved placeholder until the server acknowledges\n * the run, then gives the placeholder the server row's ID. Until then it has no creation time, and a refresh of the\n * conversation can already have brought the server's copy of the same row into the list.\n */\nimport { UUIDsEqual } from '@memberjunction/global';\nimport type { MJConversationDetailEntity } from '@memberjunction/core-entities';\n\n/** Oldest first. A message with no creation time yet (an unsaved placeholder) is the newest, not the oldest. */\nexport function CompareMessagesByCreatedAt(a: MJConversationDetailEntity, b: MJConversationDetailEntity): number {\n return createdAtOf(a) - createdAtOf(b);\n}\n\n/**\n * The list with `message` in place of the first message that has its ID, and every other copy of that ID dropped (a\n * placeholder that just took the ID of a server row the list already holds), or with `message` appended when none has\n * it. Returns whether it was already there.\n */\nexport function UpsertMessageById(\n messages: MJConversationDetailEntity[],\n message: MJConversationDetailEntity\n): { Messages: MJConversationDetailEntity[]; Existed: boolean } {\n const index = messages.findIndex((m) => UUIDsEqual(m.ID, message.ID));\n if (index < 0) {\n return { Messages: [...messages, message], Existed: false };\n }\n const updated = messages\n .map((m, i) => (i === index ? message : m))\n .filter((m, i) => i === index || !UUIDsEqual(m.ID, message.ID));\n return { Messages: updated, Existed: true };\n}\n\nfunction createdAtOf(message: MJConversationDetailEntity): number {\n return message.__mj_CreatedAt?.getTime() ?? Number.MAX_SAFE_INTEGER;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Pinning a message on a fresh copy of its row.
3
+ *
4
+ * The chat's copy of an agent reply can carry streamed or local text that differs from the stored reply, and the
5
+ * server refuses a person's save that changes a reply's content (A19), so a pin saved through that copy is refused.
6
+ * Pinning loads the row as stored and changes only `IsPinned`, as the rating component does for its fields.
7
+ */
8
+ import type { IMetadataProvider } from '@memberjunction/core';
9
+ /** Saves `IsPinned` on the stored row; false when it can't be loaded or saved. */
10
+ export declare function SavePinState(provider: IMetadataProvider, conversationDetailId: string, pinned: boolean): Promise<boolean>;
11
+ //# sourceMappingURL=pin-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pin-state.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/pin-state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,kFAAkF;AAClF,wBAAsB,YAAY,CAAC,QAAQ,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAK/H"}
@@ -0,0 +1,9 @@
1
+ /** Saves `IsPinned` on the stored row; false when it can't be loaded or saved. */
2
+ export async function SavePinState(provider, conversationDetailId, pinned) {
3
+ const fresh = await provider.GetEntityObject('MJ: Conversation Details');
4
+ if (!(await fresh.Load(conversationDetailId)))
5
+ return false;
6
+ fresh.IsPinned = pinned;
7
+ return fresh.Save();
8
+ }
9
+ //# sourceMappingURL=pin-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pin-state.js","sourceRoot":"","sources":["../../../src/lib/utils/pin-state.ts"],"names":[],"mappings":"AAUA,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAA2B,EAAE,oBAA4B,EAAE,MAAe;IAC3G,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,eAAe,CAA6B,0BAA0B,CAAC,CAAC;IACrG,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5D,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC;IACxB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC","sourcesContent":["/**\n * Pinning a message on a fresh copy of its row.\n *\n * The chat's copy of an agent reply can carry streamed or local text that differs from the stored reply, and the\n * server refuses a person's save that changes a reply's content (A19), so a pin saved through that copy is refused.\n * Pinning loads the row as stored and changes only `IsPinned`, as the rating component does for its fields.\n */\nimport type { IMetadataProvider } from '@memberjunction/core';\nimport type { MJConversationDetailEntity } from '@memberjunction/core-entities';\n\n/** Saves `IsPinned` on the stored row; false when it can't be loaded or saved. */\nexport async function SavePinState(provider: IMetadataProvider, conversationDetailId: string, pinned: boolean): Promise<boolean> {\n const fresh = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details');\n if (!(await fresh.Load(conversationDetailId))) return false;\n fresh.IsPinned = pinned;\n return fresh.Save();\n}\n"]}
@@ -8,6 +8,7 @@ export * from './lib/utils/agent-turn-routing';
8
8
  export * from './lib/utils/decision-routing';
9
9
  export * from './lib/utils/conversation-branching';
10
10
  export * from './lib/utils/conversation-forks';
11
+ export * from './lib/utils/agent-failure-display';
11
12
  export * from './lib/services/data-cache.service';
12
13
  export * from './lib/services/artifact-state.service';
13
14
  export * from './lib/services/agent-state.service';
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@ export * from './lib/utils/decision-routing';
18
18
  // Fork rules: the delete guard and the open view (Main or one fork)
19
19
  export * from './lib/utils/conversation-branching';
20
20
  export * from './lib/utils/conversation-forks';
21
+ export * from './lib/utils/agent-failure-display';
21
22
  // Services - State
22
23
  export * from './lib/services/data-cache.service';
23
24
  export * from './lib/services/artifact-state.service';