@pendo/agent 2.332.2 → 2.333.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4028,8 +4028,8 @@ let SERVER = '';
4028
4028
  let ASSET_HOST = '';
4029
4029
  let ASSET_PATH = '';
4030
4030
  let DESIGNER_SERVER = '';
4031
- let VERSION = '2.332.2_';
4032
- let PACKAGE_VERSION = '2.332.2';
4031
+ let VERSION = '2.333.1_';
4032
+ let PACKAGE_VERSION = '2.333.1';
4033
4033
  let LOADER = 'xhr';
4034
4034
  /* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
4035
4035
  /**
@@ -15080,6 +15080,16 @@ function findGuideContainerJSON(buildingBlocks) {
15080
15080
  });
15081
15081
  }
15082
15082
  }
15083
+ function correctGuideContainerId(containerJSON, stepId, guideId) {
15084
+ if (!containerJSON || !containerJSON.props || !containerJSON.props.id)
15085
+ return;
15086
+ var currentId = containerJSON.props.id;
15087
+ var isCorrect = (stepId && currentId === `pendo-g-${stepId}`) ||
15088
+ (guideId && currentId === `pendo-g-${guideId}`);
15089
+ if (!isCorrect && stepId) {
15090
+ containerJSON.props.id = `pendo-g-${stepId}`;
15091
+ }
15092
+ }
15083
15093
  function findDomBlockInDomJson(domJson, truthTestFn) {
15084
15094
  if (truthTestFn(domJson))
15085
15095
  return domJson;
@@ -29609,6 +29619,7 @@ var BuildingBlockGuides = (function () {
29609
29619
  detectColorMode(step);
29610
29620
  }
29611
29621
  var containerJSON = findGuideContainerJSON(json);
29622
+ correctGuideContainerId(containerJSON, step.id, guide.id);
29612
29623
  var isResourceCenter = _.get(guide, 'attributes.resourceCenter');
29613
29624
  if (isResourceCenter) {
29614
29625
  BuildingBlockResourceCenter.revertRtlChanges(guide, json.props);
@@ -34792,6 +34803,201 @@ var PreviewModule = (function () {
34792
34803
  };
34793
34804
  })();
34794
34805
 
34806
+ const CONTAINER_ID = 'pendo-debugger-highlight-overlay';
34807
+ const BORDER_COLOR = '#FF4876';
34808
+ const BORDER_GLOW = 'rgba(255, 72, 118, 0.35)';
34809
+ const HIGHLIGHT_Z_INDEX = '2147483644';
34810
+ const ANIMATION_DURATION_MS = 500;
34811
+ const AUTO_CLEAR_MS = 10000;
34812
+ const BORDER_STYLES = {
34813
+ 'box-sizing': 'border-box',
34814
+ 'border': `2px solid ${BORDER_COLOR}`,
34815
+ 'border-radius': '2px',
34816
+ 'box-shadow': `0 0 0 2px ${BORDER_GLOW}`,
34817
+ 'pointer-events': 'none',
34818
+ 'z-index': HIGHLIGHT_Z_INDEX
34819
+ };
34820
+ let doc = document;
34821
+ let win = window;
34822
+ let container = null;
34823
+ let targets = [];
34824
+ let overlays = [];
34825
+ let resizeObserver = null;
34826
+ let autoClearTimer = null;
34827
+ function getContainer(dom) {
34828
+ if (container && container.isConnected)
34829
+ return container;
34830
+ container = dom('<div>').css({
34831
+ 'position': 'absolute',
34832
+ 'top': 0,
34833
+ 'left': 0,
34834
+ 'width': 0,
34835
+ 'height': 0,
34836
+ 'margin': 0,
34837
+ 'padding': 0,
34838
+ 'border': 0,
34839
+ 'pointer-events': 'none'
34840
+ }).appendTo(doc.documentElement || doc.body)[0];
34841
+ container.id = CONTAINER_ID;
34842
+ return container;
34843
+ }
34844
+ function positionOverlays() {
34845
+ const scrollX = win.scrollX || win.pageXOffset || 0;
34846
+ const scrollY = win.scrollY || win.pageYOffset || 0;
34847
+ for (let i = 0; i < overlays.length; i++) {
34848
+ const { el, box } = overlays[i];
34849
+ if (!el || typeof el.getBoundingClientRect !== 'function')
34850
+ continue;
34851
+ const rect = el.getBoundingClientRect();
34852
+ box.style.top = `${rect.top + scrollY}px`;
34853
+ box.style.left = `${rect.left + scrollX}px`;
34854
+ box.style.width = `${rect.width}px`;
34855
+ box.style.height = `${rect.height}px`;
34856
+ }
34857
+ }
34858
+ function buildOverlays(dom) {
34859
+ const node = getContainer(dom);
34860
+ node.textContent = '';
34861
+ overlays = [];
34862
+ for (let i = 0; i < targets.length; i++) {
34863
+ const el = targets[i];
34864
+ if (!el || typeof el.getBoundingClientRect !== 'function')
34865
+ continue;
34866
+ const rect = el.getBoundingClientRect();
34867
+ if (rect.width === 0 && rect.height === 0)
34868
+ continue;
34869
+ const box = dom('<div>')
34870
+ .css(Object.assign({ position: 'absolute' }, BORDER_STYLES))
34871
+ .appendTo(node)[0];
34872
+ overlays.push({ el, box });
34873
+ }
34874
+ positionOverlays();
34875
+ }
34876
+ function observeLayout() {
34877
+ if (typeof win.ResizeObserver !== 'function')
34878
+ return;
34879
+ resizeObserver = new win.ResizeObserver(() => positionOverlays());
34880
+ resizeObserver.observe(doc.documentElement);
34881
+ for (let i = 0; i < overlays.length; i++) {
34882
+ resizeObserver.observe(overlays[i].el);
34883
+ }
34884
+ }
34885
+ function armAutoClear() {
34886
+ if (autoClearTimer)
34887
+ clearTimeout(autoClearTimer);
34888
+ autoClearTimer = win.setTimeout(clearHighlights, AUTO_CLEAR_MS);
34889
+ }
34890
+ function isInViewport(rect) {
34891
+ const vh = win.innerHeight || doc.documentElement.clientHeight;
34892
+ const vw = win.innerWidth || doc.documentElement.clientWidth;
34893
+ return rect.bottom > 0 && rect.right > 0 && rect.top < vh && rect.left < vw;
34894
+ }
34895
+ function scrollIntoViewIfNeeded(elements) {
34896
+ const anyVisible = elements.some(el => isInViewport(el.getBoundingClientRect()));
34897
+ if (anyVisible)
34898
+ return;
34899
+ const first = elements[0];
34900
+ if (first && typeof first.scrollIntoView === 'function') {
34901
+ first.scrollIntoView({ block: 'center', inline: 'center' });
34902
+ }
34903
+ }
34904
+ function animateFocusRing(dom, el) {
34905
+ if (!el || typeof el.animate !== 'function')
34906
+ return;
34907
+ const rect = el.getBoundingClientRect();
34908
+ if (rect.width === 0 && rect.height === 0)
34909
+ return;
34910
+ const ring = dom('<div>')
34911
+ .css(Object.assign({ position: 'fixed' }, BORDER_STYLES))
34912
+ .appendTo(doc.documentElement || doc.body)[0];
34913
+ const animation = ring.animate([
34914
+ { top: '0px', left: '0px', width: '100vw', height: '100vh', borderRadius: '0px', opacity: 0 },
34915
+ { opacity: 1, offset: 0.2 },
34916
+ {
34917
+ top: `${rect.top}px`,
34918
+ left: `${rect.left}px`,
34919
+ width: `${rect.width}px`,
34920
+ height: `${rect.height}px`,
34921
+ borderRadius: '2px',
34922
+ opacity: 1
34923
+ }
34924
+ ], { duration: ANIMATION_DURATION_MS, easing: 'ease-in-out' });
34925
+ animation.onfinish = () => {
34926
+ if (ring.parentNode)
34927
+ ring.parentNode.removeChild(ring);
34928
+ };
34929
+ }
34930
+ function highlight(dom, elements) {
34931
+ clearHighlights();
34932
+ targets = [];
34933
+ for (let i = 0; i < elements.length; i++) {
34934
+ const el = elements[i];
34935
+ if (el && el.nodeType === 1)
34936
+ targets.push(el);
34937
+ }
34938
+ if (!targets.length)
34939
+ return 0;
34940
+ doc = targets[0].ownerDocument || document;
34941
+ win = doc.defaultView || window;
34942
+ scrollIntoViewIfNeeded(targets);
34943
+ buildOverlays(dom);
34944
+ observeLayout();
34945
+ animateFocusRing(dom, targets[0]);
34946
+ armAutoClear();
34947
+ return targets.length;
34948
+ }
34949
+ function highlightElements(dom, selector) {
34950
+ try {
34951
+ const query = dom(selector);
34952
+ const total = query ? query.length : 0;
34953
+ const elements = [];
34954
+ for (let i = 0; i < total; i++) {
34955
+ elements.push(query[i]);
34956
+ }
34957
+ return highlight(dom, elements);
34958
+ }
34959
+ catch (e) {
34960
+ clearHighlights();
34961
+ return 0;
34962
+ }
34963
+ }
34964
+ function highlightFrameElement(dom) {
34965
+ let frameEl = null;
34966
+ try {
34967
+ frameEl = window.frameElement;
34968
+ }
34969
+ catch (e) {
34970
+ frameEl = null;
34971
+ }
34972
+ if (frameEl) {
34973
+ try {
34974
+ return highlight(dom, [frameEl]);
34975
+ }
34976
+ catch (e) {
34977
+ clearHighlights();
34978
+ return 0;
34979
+ }
34980
+ }
34981
+ return highlightElements(dom, 'body');
34982
+ }
34983
+ function clearHighlights() {
34984
+ if (autoClearTimer) {
34985
+ clearTimeout(autoClearTimer);
34986
+ autoClearTimer = null;
34987
+ }
34988
+ if (resizeObserver) {
34989
+ resizeObserver.disconnect();
34990
+ resizeObserver = null;
34991
+ }
34992
+ targets = [];
34993
+ overlays = [];
34994
+ if (container) {
34995
+ if (container.parentNode)
34996
+ container.parentNode.removeChild(container);
34997
+ container = null;
34998
+ }
34999
+ }
35000
+
34795
35001
  const DebuggerModule = (() => {
34796
35002
  const state = {
34797
35003
  isLeader: undefined,
@@ -34973,6 +35179,49 @@ const DebuggerModule = (() => {
34973
35179
  }).then(result => JSON.parse(result));
34974
35180
  });
34975
35181
  q.all(framePromises).then(results => response(results));
35182
+ },
35183
+ highlightElement(context, payload) {
35184
+ const { selector, targetFrame, outlineFrame, response } = payload;
35185
+ const frames = targetFrame
35186
+ ? _.filter(store.state.frames.frames, frame => frame.id === targetFrame)
35187
+ : _.values(store.state.frames.frames);
35188
+ const framePromises = _.map(frames, frame => {
35189
+ return crossFrameChannel.postMessageOrTimeout({
35190
+ action: 'debugger/highlightInFrame',
35191
+ targetFrame: frame.id,
35192
+ selector,
35193
+ outlineFrame
35194
+ }).then(result => JSON.parse(result)).catch(() => ({ count: 0 }));
35195
+ });
35196
+ q.all(framePromises).then(results => {
35197
+ const count = _.reduce(results, (sum, result) => sum + (result.count || 0), 0);
35198
+ if (response)
35199
+ response({ count });
35200
+ });
35201
+ },
35202
+ clearHighlight() {
35203
+ crossFrameChannel.postMessage({
35204
+ action: 'debugger/clearHighlightInFrame'
35205
+ });
35206
+ },
35207
+ highlightInFrame(context, payload) {
35208
+ const { selector, outlineFrame, sourceFrame, promiseId } = payload;
35209
+ const count = outlineFrame
35210
+ ? highlightFrameElement(pendo$1.dom)
35211
+ : highlightElements(pendo$1.dom, selector);
35212
+ if (promiseId && sourceFrame) {
35213
+ crossFrameChannel.postMessage({
35214
+ action: 'debuggerHighlightResponse',
35215
+ targetFrame: sourceFrame,
35216
+ resolveId: promiseId,
35217
+ frameId: context.state.frameId,
35218
+ count
35219
+ });
35220
+ }
35221
+ return { count };
35222
+ },
35223
+ clearHighlightInFrame() {
35224
+ clearHighlights();
34976
35225
  }
34977
35226
  };
34978
35227
  const getters = {};
@@ -37848,6 +38097,94 @@ const IFrameMonitor = (function () {
37848
38097
  }
37849
38098
  })();
37850
38099
 
38100
+ const MAX_TRACKED_PAIRS = 10;
38101
+ const ObjectMetadata = (function () {
38102
+ let globalPendo;
38103
+ let pluginApi;
38104
+ // {objectId, type} pair -> crc32(metadata), persisted so we don't resend unchanged object
38105
+ // metadata across page loads. This is purely a send-dedup cache and is not tied to visitor
38106
+ // identity. Map insertion order gives us LRU eviction: re-storing a pair moves it to newest.
38107
+ let sentHashes;
38108
+ /**
38109
+ * Persisted crc32 hashes of the last object metadata sent for each {objectId, type} pair. Used to avoid
38110
+ * resending unchanged object metadata across page loads.
38111
+ *
38112
+ * @name _pendo_objectMetadata
38113
+ * @category Cookies/localStorage
38114
+ * @access public
38115
+ * @label OBJECT_METADATA
38116
+ */
38117
+ var OBJECT_METADATA = 'objectMetadata';
38118
+ /**
38119
+ * Sends metadata for an arbitrary object, identified by an id and type. Unlike `updateOptions`, which
38120
+ * targets the current visitor/account, this associates metadata with a caller-defined object. The metadata
38121
+ * is only sent when it differs from the last payload sent for the same object id + type, so calling this
38122
+ * repeatedly with unchanged metadata is a no-op.
38123
+ *
38124
+ * @access public
38125
+ * @function
38126
+ * @category Identity
38127
+ * @param {string} objectId the object's id
38128
+ * @param {string} type the object's type
38129
+ * @param {object} metadata the metadata payload to associate with the object
38130
+ * @example
38131
+ * pendo.objectMetadata('order-1234', 'order', { total: 42.5, status: 'shipped' });
38132
+ */
38133
+ function objectMetadata(objectId, type, metadata) {
38134
+ if (!isValidValue(objectId) || !isValidValue(type)) {
38135
+ pluginApi.log.warn('objectMetadata requires a non-empty objectId and type.');
38136
+ return;
38137
+ }
38138
+ var key = JSON.stringify([objectId, type]);
38139
+ var hash = crc32(metadata);
38140
+ if (sentHashes.get(key) === hash) {
38141
+ // Unchanged since the last send; refresh recency and skip.
38142
+ sentHashes.delete(key);
38143
+ sentHashes.set(key, hash);
38144
+ return;
38145
+ }
38146
+ sentHashes.delete(key);
38147
+ sentHashes.set(key, hash);
38148
+ if (sentHashes.size > MAX_TRACKED_PAIRS) {
38149
+ sentHashes.delete(sentHashes.keys().next().value);
38150
+ }
38151
+ persistHashes();
38152
+ pluginApi.analytics.collectEvent('objectMetadata', { objectId, type, metadata });
38153
+ globalPendo.buffers.flushLater();
38154
+ }
38155
+ return {
38156
+ name: 'ObjectMetadata',
38157
+ initialize,
38158
+ teardown,
38159
+ objectMetadata
38160
+ };
38161
+ function initialize(pendo, PluginAPI) {
38162
+ globalPendo = pendo;
38163
+ pluginApi = PluginAPI;
38164
+ pluginApi.agentStorage.registry.addLocal(OBJECT_METADATA, {
38165
+ isSecure: true,
38166
+ localStorageOnly: true,
38167
+ serializer: JSON.stringify,
38168
+ deserializer: SafeJsonDeserializer
38169
+ });
38170
+ sentHashes = loadHashes();
38171
+ globalPendo.objectMetadata = objectMetadata;
38172
+ }
38173
+ function teardown() {
38174
+ delete globalPendo.objectMetadata;
38175
+ }
38176
+ function loadHashes() {
38177
+ var stored = pluginApi.agentStorage.read(OBJECT_METADATA);
38178
+ return Array.isArray(stored) ? new Map(stored) : new Map();
38179
+ }
38180
+ function persistHashes() {
38181
+ pluginApi.agentStorage.write(OBJECT_METADATA, Array.from(sentHashes));
38182
+ }
38183
+ function isValidValue(value) {
38184
+ return typeof value === 'string' && value.length > 0;
38185
+ }
38186
+ })();
38187
+
37851
38188
  /**
37852
38189
  * OEM Account ID Plugin
37853
38190
  *
@@ -41072,6 +41409,7 @@ function registerBuiltInPlugins() {
41072
41409
  registerPlugin(FrustrationEvent);
41073
41410
  registerPlugin(GuideCachePlugin);
41074
41411
  registerPlugin(IFrameMonitor);
41412
+ registerPlugin(ObjectMetadata);
41075
41413
  registerPlugin(OemAccountId);
41076
41414
  registerPlugin(P1GuidePlugin);
41077
41415
  registerPlugin(PromoteMetadata);
@@ -51282,12 +51620,12 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51282
51620
  }
51283
51621
  }
51284
51622
 
51285
- var ONE_HUNDRED_MB_IN_BYTES = 100 * 1024 * 1024;
51623
+ const ONE_HUNDRED_MB_IN_BYTES = 100 * 1024 * 1024;
51286
51624
  function matchHostedResources(recordingEvent, stringifiedRecordingPayload) {
51287
- var fontURLRegex = /https:\/\/[^"\\\s]+?\.(woff2?|ttf|otf|eot)(\?[^"\\\s]*)?\b/g;
51288
- var matches = stringifiedRecordingPayload.match(fontURLRegex);
51625
+ const fontURLRegex = /https:\/\/[^"\\\s]+?\.(woff2?|ttf|otf|eot)(\?[^"\\\s]*)?\b/g;
51626
+ const matches = stringifiedRecordingPayload.match(fontURLRegex);
51289
51627
  // de-duplicate matches
51290
- var hostedResources = matches ? Array.from(new Set(matches)) : [];
51628
+ const hostedResources = matches ? Array.from(new Set(matches)) : [];
51291
51629
  return {
51292
51630
  type: 'recording',
51293
51631
  visitorId: recordingEvent.visitorId,
@@ -51302,7 +51640,7 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51302
51640
  recordingPayloadMetadata: [],
51303
51641
  sequence: 0,
51304
51642
  recordingPayloadCount: 0,
51305
- hostedResources: hostedResources
51643
+ hostedResources
51306
51644
  };
51307
51645
  }
51308
51646
  // keep in mind changes here may need addressing in the extension worker proxy as well
@@ -51310,69 +51648,69 @@ var WorkerFactory = /*#__PURE__*/createInlineWorkerFactory(/* rollup-plugin-web-
51310
51648
  try {
51311
51649
  if (e.data.type === 'init' && e.data.lockID) {
51312
51650
  if (navigator.locks) {
51313
- navigator.locks.request(e.data.lockID, function () {
51651
+ navigator.locks.request(e.data.lockID, () => {
51314
51652
  postMessage({ ready: true });
51315
51653
  // This unresolved promise keeps the lock held with the worker until the worker is
51316
51654
  // terminated at which point the lock is released.
51317
- return new Promise$2(function () { });
51655
+ return new Promise$2(() => { });
51318
51656
  });
51319
51657
  }
51320
51658
  return;
51321
51659
  }
51322
51660
  if (e.data.url && e.data.payload) {
51323
- var _a = e.data, url = _a.url, payload = _a.payload, shouldCacheResources = _a.shouldCacheResources;
51661
+ let { url, payload, shouldCacheResources } = e.data;
51324
51662
  if (Object.keys(payload).length === 0) {
51325
51663
  postMessage({
51326
51664
  type: 'emptyPayload'
51327
51665
  });
51328
51666
  }
51329
- var sequence_1 = payload.sequence;
51330
- var stringifiedRecordingPayload = JSON.stringify(payload.recordingPayload);
51667
+ const { sequence } = payload;
51668
+ const stringifiedRecordingPayload = JSON.stringify(payload.recordingPayload);
51331
51669
  // calculate and post back the recording payload size before the payload is compressed
51332
- var recordingPayloadSize = new TextEncoder().encode(stringifiedRecordingPayload).length;
51333
- var exceedsPayloadSizeLimit = recordingPayloadSize >= ONE_HUNDRED_MB_IN_BYTES;
51670
+ const recordingPayloadSize = new TextEncoder().encode(stringifiedRecordingPayload).length;
51671
+ const exceedsPayloadSizeLimit = recordingPayloadSize >= ONE_HUNDRED_MB_IN_BYTES;
51334
51672
  postMessage({
51335
51673
  type: 'recordingPayloadSize',
51336
- recordingPayloadSize: recordingPayloadSize,
51337
- exceedsPayloadSizeLimit: exceedsPayloadSizeLimit
51674
+ recordingPayloadSize,
51675
+ exceedsPayloadSizeLimit
51338
51676
  });
51339
51677
  // don't attempt to send the payload if it exceeds the allowed limit
51340
51678
  if (exceedsPayloadSizeLimit)
51341
51679
  return;
51342
51680
  if (shouldCacheResources) {
51343
- var hostedResourcesEvent = matchHostedResources(payload, stringifiedRecordingPayload);
51681
+ const hostedResourcesEvent = matchHostedResources(payload, stringifiedRecordingPayload);
51344
51682
  if (hostedResourcesEvent.hostedResources.length) {
51345
51683
  postMessage({
51346
51684
  type: 'hostedResources',
51347
- hostedResourcesEvent: hostedResourcesEvent
51685
+ hostedResourcesEvent
51348
51686
  });
51349
51687
  }
51350
51688
  }
51351
51689
  payload.recordingSize = recordingPayloadSize;
51352
- var body = compress(payload, 'binary');
51690
+ const body = compress(payload, 'binary');
51353
51691
  // we want to calculate ct (current time) as close as we can to the actual POST request
51354
51692
  // so that it's as accurate as possible
51355
- url = "".concat(url, "&ct=").concat(new Date().getTime());
51693
+ url = `${url}&ct=${new Date().getTime()}`;
51356
51694
  fetch(url, {
51357
51695
  method: 'POST',
51358
- body: body
51696
+ body
51359
51697
  }).then(function (response) {
51360
51698
  if (response.status < 200 || response.status >= 300) {
51361
51699
  postMessage({
51362
- error: new Error("received status code ".concat(response.status, ": ").concat(response.statusText)),
51700
+ error: new Error(`received status code ${response.status}: ${response.statusText}`),
51363
51701
  status: response.status,
51364
- sequence: sequence_1
51702
+ sequence
51365
51703
  });
51366
51704
  }
51367
51705
  else {
51368
51706
  postMessage({
51369
- sequence: sequence_1
51707
+ sequence
51370
51708
  });
51371
51709
  }
51372
- })["catch"](function (e) {
51710
+ }).catch(function (e) {
51373
51711
  postMessage({
51374
51712
  error: e,
51375
- sequence: sequence_1
51713
+ sequence
51376
51714
  });
51377
51715
  });
51378
51716
  }
@@ -55253,10 +55591,18 @@ const REQUIRED_SELECTORS = [
55253
55591
  TURN_CONTAINER,
55254
55592
  USER_MESSAGE
55255
55593
  ];
55256
- const SUPPORTED_PRESETS = ['chatgpt-full', 'gemini-full', 'claude-full', 'copilot-full'];
55594
+ const SUPPORTED_PRESETS = [
55595
+ 'chatgpt-full',
55596
+ 'gemini-full',
55597
+ 'claude-full',
55598
+ 'copilot-full',
55599
+ 'copilot-m365-full'
55600
+ ];
55257
55601
  const STREAMING_END_SETTLE_MS = 500;
55258
55602
  const SUBMISSION_START_RETRY_MS = 1000;
55259
55603
  const SUBMISSION_START_MAX_ATTEMPTS = 4;
55604
+ const NEW_CONVERSATION_TTL_MS = 5 * 60 * 1000;
55605
+ const NEW_CONVERSATION_KEY = 'pendo_pca_new_conv';
55260
55606
  class DOMConversation {
55261
55607
  // Returns the list of required selector types that are missing or empty
55262
55608
  // in the given cssSelectors config. An empty array means the config is
@@ -55368,6 +55714,21 @@ class DOMConversation {
55368
55714
  content
55369
55715
  });
55370
55716
  }
55717
+ // On a new M365 conversation the browser does a full page reload to the
55718
+ // conversation URL. the old page sets FRESH_CONVERSATION_KEY when streaming starts
55719
+ // without a conversation id; the new page picks it up here and triggers onSubmissionEnd
55720
+ // to scan the already-rendered messages.
55721
+ tryFlushNewConversation() {
55722
+ const ts = +this.api.sessionStorage.getItem(NEW_CONVERSATION_KEY);
55723
+ if (!ts)
55724
+ return;
55725
+ this.api.sessionStorage.removeItem(NEW_CONVERSATION_KEY);
55726
+ if (Date.now() - ts > NEW_CONVERSATION_TTL_MS)
55727
+ return;
55728
+ if (!this.getConversationId())
55729
+ return;
55730
+ this.startStreamingEndTimer();
55731
+ }
55371
55732
  // observe the streaming indicator
55372
55733
  setupStreamingObserver() {
55373
55734
  this.wasStreaming = this.select(STREAMING_ACTIVE).length > 0;
@@ -55397,6 +55758,11 @@ class DOMConversation {
55397
55758
  return;
55398
55759
  if (isStreaming) { // started streaming
55399
55760
  if (!this.streamingEndTimer) {
55761
+ if (!this.getConversationId()) {
55762
+ // Full-page reload expected on fresh chat: persist a flag so the
55763
+ // new page can emit the prompt + response after the reload.
55764
+ this.api.sessionStorage.setItem(NEW_CONVERSATION_KEY, String(Date.now()));
55765
+ }
55400
55766
  this.onSubmissionStart();
55401
55767
  }
55402
55768
  else {
@@ -55425,6 +55791,9 @@ class DOMConversation {
55425
55791
  }
55426
55792
  onSubmissionEnd() {
55427
55793
  this.stopSubmissionStartRetryTimer();
55794
+ if (this.getConversationId()) {
55795
+ this.api.sessionStorage.removeItem(NEW_CONVERSATION_KEY);
55796
+ }
55428
55797
  let requestNode = this.requestNode;
55429
55798
  this.requestNode = null;
55430
55799
  if (!requestNode) {
@@ -55612,6 +55981,7 @@ class DOMConversation {
55612
55981
  }
55613
55982
  resume() {
55614
55983
  this.isActive = true;
55984
+ this.tryFlushNewConversation();
55615
55985
  }
55616
55986
  teardown() {
55617
55987
  this._.each(this.observers, (o) => o && o.disconnect && o.disconnect());