@pendo/agent 2.328.2 → 2.330.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom.esm.js +2 -2
- package/dist/pendo.module.js +573 -513
- package/dist/pendo.module.min.js +11 -11
- package/dist/servers.json +7 -7
- package/index.js +1 -1
- package/package.json +1 -1
package/dist/pendo.module.js
CHANGED
|
@@ -3403,7 +3403,7 @@ var ConfigReader = (function () {
|
|
|
3403
3403
|
* cookies, localStorage, click tracking, and metadata. Only displays guides
|
|
3404
3404
|
* that have been configured explicitly to show to this visitor.
|
|
3405
3405
|
*
|
|
3406
|
-
* @access
|
|
3406
|
+
* @access public
|
|
3407
3407
|
* @category Config/Guides
|
|
3408
3408
|
* @name doNotTrackGuides
|
|
3409
3409
|
* @default false
|
|
@@ -4010,8 +4010,8 @@ let SERVER = '';
|
|
|
4010
4010
|
let ASSET_HOST = '';
|
|
4011
4011
|
let ASSET_PATH = '';
|
|
4012
4012
|
let DESIGNER_SERVER = '';
|
|
4013
|
-
let VERSION = '2.
|
|
4014
|
-
let PACKAGE_VERSION = '2.
|
|
4013
|
+
let VERSION = '2.330.0_';
|
|
4014
|
+
let PACKAGE_VERSION = '2.330.0';
|
|
4015
4015
|
let LOADER = 'xhr';
|
|
4016
4016
|
/* eslint-enable web-sdk-eslint-rules/no-gulp-env-references */
|
|
4017
4017
|
/**
|
|
@@ -9750,7 +9750,11 @@ function exportDataHost(pendo) {
|
|
|
9750
9750
|
pendo.HOST = HOST;
|
|
9751
9751
|
}
|
|
9752
9752
|
function getAssetHost() {
|
|
9753
|
-
|
|
9753
|
+
var assetHost = ConfigReader.get('assetHost');
|
|
9754
|
+
if (assetHost) {
|
|
9755
|
+
return assetHost.indexOf('://') === -1 ? 'https://' + assetHost : assetHost;
|
|
9756
|
+
}
|
|
9757
|
+
return getContentHost();
|
|
9754
9758
|
}
|
|
9755
9759
|
function getContentHost() {
|
|
9756
9760
|
var protocol = 'https://';
|
|
@@ -11752,13 +11756,13 @@ function isTrustedOrigin(host) {
|
|
|
11752
11756
|
// Domains that Pendo owns, will be swapped in by build patches
|
|
11753
11757
|
const patterns = [
|
|
11754
11758
|
/^https:\/\/(adopt\.)?((us1)\.)?(app|via|adopt|cdn|oem|novus-api|novus)(\.(au|eu|gov|hsbc|jpn))?\.pendo\.io$/,
|
|
11755
|
-
/^https:\/\/([0-9]{8}t[0-9]{4}-dot-)pendo-
|
|
11756
|
-
/^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)pendo-
|
|
11757
|
-
/^https:\/\/pendo-
|
|
11759
|
+
/^https:\/\/([0-9]{8}t[0-9]{4}-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-us1)\.appspot\.com$/,
|
|
11760
|
+
/^https:\/\/hotfix-(ops|app)-([0-9]+-dot-)(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-us1)\.appspot\.com$/,
|
|
11761
|
+
/^https:\/\/(pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-us1)-static\.storage\.googleapis\.com$/,
|
|
11758
11762
|
/^https:\/\/([a-zA-Z0-9-]+\.)*pendo-dev\.com$/,
|
|
11759
|
-
/^https:\/\/([a-zA-Z0-9-]+-dot-)?pendo-
|
|
11763
|
+
/^https:\/\/([a-zA-Z0-9-]+-dot-)?(pendo-apollo|pendo-armada|pendo-atlas|pendo-batman|pendo-calypso|pendo-dap|pendo-dev|pendo-dr-us-east1|pendo-eu-dev|pendo-freeze|pendo-helix|pendo-link|pendo-magic|pendo-mango|pendo-mcfly|pendo-ml|pendo-mobile-fbi|pendo-mobile-guides|pendo-mobile-hummus|pendo-mobile-plat|pendo-perfserf|pendo-voc|pendo-wildlings|pendo-ionchef|pendo-scrum-ops|pendo-security|pendo-test|pendo-uat|pendo-au|pendo-eu|pendo-govramp|pendo-hsbc|pendo-io|pendo-jp-prod|pendo-us1)\.appspot\.com$/,
|
|
11760
11764
|
/^https:\/\/(via|adopt|local)\.pendo\.(local|io):\d{4}$/,
|
|
11761
|
-
/^https:\/\/pendo-
|
|
11765
|
+
/^https:\/\/(pendo-apollo|pendo-armada|pendo-atlas|pendo-batman|pendo-calypso|pendo-dap|pendo-dev|pendo-dr-us-east1|pendo-eu-dev|pendo-freeze|pendo-helix|pendo-link|pendo-magic|pendo-mango|pendo-mcfly|pendo-ml|pendo-mobile-fbi|pendo-mobile-guides|pendo-mobile-hummus|pendo-mobile-plat|pendo-perfserf|pendo-voc|pendo-wildlings|pendo-ionchef|pendo-scrum-ops|pendo-security|pendo-test|pendo-uat)-static\.storage\.googleapis\.com$/
|
|
11762
11766
|
]; // eslint-disable-line web-sdk-eslint-rules/no-gulp-env-references
|
|
11763
11767
|
var adoptHost = ConfigReader.get('adoptHost');
|
|
11764
11768
|
if (adoptHost) {
|
|
@@ -11846,7 +11850,9 @@ var trustedOriginForLoadResource = function (origin) {
|
|
|
11846
11850
|
return isTrustedOrigin(origin);
|
|
11847
11851
|
};
|
|
11848
11852
|
var parseUrlOrigin = function (url) {
|
|
11849
|
-
|
|
11853
|
+
// Resolve against the document URL so relative asset urls (used when self-hosting
|
|
11854
|
+
// with an empty asset host) parse to the current page origin instead of throwing.
|
|
11855
|
+
return new URL(url, window.location.href).origin;
|
|
11850
11856
|
};
|
|
11851
11857
|
var loadResource = function (options, callback, sendErrorToCallback = false) {
|
|
11852
11858
|
try {
|
|
@@ -20097,6 +20103,7 @@ var GuideStateModule = (function () {
|
|
|
20097
20103
|
storageKey: LAST_STEP_ADVANCED_COOKIE,
|
|
20098
20104
|
guideRequestExpiration: 60 * 60 * 1000,
|
|
20099
20105
|
scopedStorageKey: null,
|
|
20106
|
+
scopedThrottlingStorageKeys: {},
|
|
20100
20107
|
receivedStateChangeAt: null,
|
|
20101
20108
|
receivedLastGuideStepSeen: null,
|
|
20102
20109
|
lastGuideStepSeen: null,
|
|
@@ -20119,6 +20126,11 @@ var GuideStateModule = (function () {
|
|
|
20119
20126
|
*/
|
|
20120
20127
|
context.getters.storage().registry.addLocal(LAST_STEP_ADVANCED_COOKIE);
|
|
20121
20128
|
context.commit('setScopedStorageKey', getPendoCookieKey(context.state.storageKey));
|
|
20129
|
+
var scopedThrottlingStorageKeys = {};
|
|
20130
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20131
|
+
scopedThrottlingStorageKeys[throttlingStorageKey] = getPendoCookieKey(throttlingStorageKey);
|
|
20132
|
+
});
|
|
20133
|
+
context.commit('setScopedThrottlingStorageKeys', scopedThrottlingStorageKeys);
|
|
20122
20134
|
Events.identify.on(function (evt) {
|
|
20123
20135
|
if (wasSessionCleared(evt)) {
|
|
20124
20136
|
removeIdentificationCookies([LAST_STEP_ADVANCED_COOKIE]);
|
|
@@ -20157,11 +20169,21 @@ var GuideStateModule = (function () {
|
|
|
20157
20169
|
});
|
|
20158
20170
|
}
|
|
20159
20171
|
}
|
|
20172
|
+
context.dispatch('loadThrottling');
|
|
20173
|
+
},
|
|
20174
|
+
loadThrottling(context) {
|
|
20175
|
+
const storage = context.getters.storage();
|
|
20160
20176
|
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20161
20177
|
const value = storage.read(throttlingStorageKey);
|
|
20162
20178
|
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value });
|
|
20163
20179
|
});
|
|
20164
20180
|
},
|
|
20181
|
+
applyThrottling(context) {
|
|
20182
|
+
_.each(THROTTLING_STATE, function (throttlingStorageKey) {
|
|
20183
|
+
pendo$1[throttlingStorageKey] = applyTimerCache(pendo$1[throttlingStorageKey], context.state[throttlingStorageKey]);
|
|
20184
|
+
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value: pendo$1[throttlingStorageKey] });
|
|
20185
|
+
});
|
|
20186
|
+
},
|
|
20165
20187
|
forceExpire(context) {
|
|
20166
20188
|
_.each(context.state.steps, function (stepState, stepId) {
|
|
20167
20189
|
context.commit('expireStepState', stepId);
|
|
@@ -20237,10 +20259,7 @@ var GuideStateModule = (function () {
|
|
|
20237
20259
|
if (mostRecentLastGuideStepSeen) {
|
|
20238
20260
|
context.dispatch('updateLastGuideStepSeen', _.extend({}, context.state.lastGuideStepSeen, mostRecentLastGuideStepSeen));
|
|
20239
20261
|
}
|
|
20240
|
-
|
|
20241
|
-
pendo$1[throttlingStorageKey] = applyTimerCache(pendo$1[throttlingStorageKey], context.state[throttlingStorageKey]);
|
|
20242
|
-
context.dispatch('updateThrottlingState', { name: throttlingStorageKey, value: pendo$1[throttlingStorageKey] });
|
|
20243
|
-
});
|
|
20262
|
+
context.dispatch('applyThrottling');
|
|
20244
20263
|
},
|
|
20245
20264
|
receiveLastGuideStepSeen(context, lastGuideStepSeen) {
|
|
20246
20265
|
context.commit('setReceivedLastGuideStepSeen', lastGuideStepSeen);
|
|
@@ -20303,6 +20322,10 @@ var GuideStateModule = (function () {
|
|
|
20303
20322
|
}
|
|
20304
20323
|
}
|
|
20305
20324
|
}
|
|
20325
|
+
else if (_.contains(_.values(context.state.scopedThrottlingStorageKeys), storageEvent.key)) {
|
|
20326
|
+
context.dispatch('loadThrottling');
|
|
20327
|
+
context.dispatch('applyThrottling');
|
|
20328
|
+
}
|
|
20306
20329
|
},
|
|
20307
20330
|
regainFocus(context) {
|
|
20308
20331
|
var tabLostFocus = context.getters.tabLostFocus();
|
|
@@ -20330,6 +20353,9 @@ var GuideStateModule = (function () {
|
|
|
20330
20353
|
setScopedStorageKey(state, scopedStorageKey) {
|
|
20331
20354
|
state.scopedStorageKey = scopedStorageKey;
|
|
20332
20355
|
},
|
|
20356
|
+
setScopedThrottlingStorageKeys(state, scopedThrottlingStorageKeys) {
|
|
20357
|
+
state.scopedThrottlingStorageKeys = scopedThrottlingStorageKeys;
|
|
20358
|
+
},
|
|
20333
20359
|
setLastGuideStepSeen(state, lastGuideStepSeen) {
|
|
20334
20360
|
state.lastGuideStepSeen = lastGuideStepSeen;
|
|
20335
20361
|
},
|
|
@@ -23570,13 +23596,13 @@ var onGuideDismissed = function (evt, step) {
|
|
|
23570
23596
|
var seenReason = firstStep && firstStep.seenReason;
|
|
23571
23597
|
var language = currentGuide && currentGuide.language;
|
|
23572
23598
|
var dismissCount = _.get(step, 'dismissCount', 0) + 1;
|
|
23573
|
-
dismissedGuide(guideId, stepId, get_visitor_id(), seenReason, language, dismissCount);
|
|
23574
23599
|
var now = getNow();
|
|
23575
|
-
|
|
23576
|
-
// maintain latestDismissedAutoAt client-side
|
|
23600
|
+
// maintain latestDismissedAutoAt client-side before guideDismissed writes lastStepAdvanced
|
|
23577
23601
|
if (shouldAffectThrottling(currentGuide, seenReason)) {
|
|
23578
23602
|
writeThrottlingStateCache(now, THROTTLING_STATE.DISMISSED);
|
|
23579
23603
|
}
|
|
23604
|
+
dismissedGuide(guideId, stepId, get_visitor_id(), seenReason, language, dismissCount);
|
|
23605
|
+
_updateGuideStepStatus(guideId, stepId, 'dismissed', now, dismissCount);
|
|
23580
23606
|
step.hide();
|
|
23581
23607
|
if (!isGuideShown()) {
|
|
23582
23608
|
stopGuides();
|
|
@@ -23632,12 +23658,12 @@ var onGuideSnoozed = function (evt, step, snoozeDuration) {
|
|
|
23632
23658
|
log.info('snoozing guide for ' + snoozeDuration + ' ms');
|
|
23633
23659
|
var snoozeEndTime = now + snoozeDuration;
|
|
23634
23660
|
step.snoozeEndTime = snoozeEndTime;
|
|
23635
|
-
|
|
23636
|
-
_updateGuideStepStatus(guideId, stepId, 'snoozed', now);
|
|
23637
|
-
// maintain latestSnoozedAutoAt client-side
|
|
23661
|
+
// maintain latestSnoozedAutoAt client-side before guideSnoozed writes lastStepAdvanced
|
|
23638
23662
|
if (shouldAffectThrottling(guide, seenReason)) {
|
|
23639
23663
|
writeThrottlingStateCache(now, THROTTLING_STATE.SNOOZED);
|
|
23640
23664
|
}
|
|
23665
|
+
snoozedGuide(guideId, stepId, visitorId, seenReason, language, snoozeDuration);
|
|
23666
|
+
_updateGuideStepStatus(guideId, stepId, 'snoozed', now);
|
|
23641
23667
|
step.hide();
|
|
23642
23668
|
if (!isGuideShown()) {
|
|
23643
23669
|
stopGuides();
|
|
@@ -23786,15 +23812,15 @@ var onGuideAdvanced = function (evt, step, isIntermediateStep) {
|
|
|
23786
23812
|
if (currentGuide && _.last(currentGuide.steps).id === stepId) {
|
|
23787
23813
|
dismissCount = _.get(step, 'dismissCount', 0) + 1;
|
|
23788
23814
|
}
|
|
23789
|
-
log.info('advancing guide');
|
|
23790
|
-
advancedGuide(guideId, stepId, get_visitor_id(), seenReason, language, isIntermediateStep, destinationStepId, dismissCount);
|
|
23791
|
-
log.info('update guide status');
|
|
23792
23815
|
var now = new Date().getTime();
|
|
23793
|
-
|
|
23794
|
-
// maintain finalAdvancedAutoAt client-side
|
|
23816
|
+
// maintain finalAdvancedAutoAt client-side before guideAdvanced writes lastStepAdvanced
|
|
23795
23817
|
if (shouldAffectThrottling(currentGuide, seenReason)) {
|
|
23796
23818
|
writeThrottlingStateCache(now, THROTTLING_STATE.ADVANCED);
|
|
23797
23819
|
}
|
|
23820
|
+
log.info('advancing guide');
|
|
23821
|
+
advancedGuide(guideId, stepId, get_visitor_id(), seenReason, language, isIntermediateStep, destinationStepId, dismissCount);
|
|
23822
|
+
log.info('update guide status');
|
|
23823
|
+
_updateGuideStepStatus(guideId, stepId, 'advanced', now, dismissCount, destinationStepId);
|
|
23798
23824
|
log.info('stop guide');
|
|
23799
23825
|
stopGuides();
|
|
23800
23826
|
log.info('start guides');
|
|
@@ -26035,7 +26061,7 @@ function getWhiteLabelSettings(config) {
|
|
|
26035
26061
|
var whiteLabelSettings = ConfigReader.get('whiteLabelSettings');
|
|
26036
26062
|
if (!whiteLabelSettings && config && config.ux === 'novus') {
|
|
26037
26063
|
whiteLabelSettings = {
|
|
26038
|
-
logoUrl: 'https://novus.pendo.io/novus-
|
|
26064
|
+
logoUrl: 'https://novus.pendo.io/branding/novus-vector-white.svg',
|
|
26039
26065
|
logoStyle: {
|
|
26040
26066
|
maxWidth: '40px',
|
|
26041
26067
|
maxHeight: '40px'
|
|
@@ -28505,6 +28531,7 @@ const PluginAPI = {
|
|
|
28505
28531
|
trim,
|
|
28506
28532
|
base64EncodeString,
|
|
28507
28533
|
parseUrl,
|
|
28534
|
+
testPageRule,
|
|
28508
28535
|
addInlineStyles: _.partial(addInlineStyles, ConfigReader),
|
|
28509
28536
|
getNow,
|
|
28510
28537
|
escapeRegExp
|
|
@@ -30346,10 +30373,8 @@ function track(name, props, eventProperties) {
|
|
|
30346
30373
|
collectEventHelper({ type: 'track', name, props, eventProperties });
|
|
30347
30374
|
}
|
|
30348
30375
|
|
|
30349
|
-
const privacyFilterCache = new Map();
|
|
30350
30376
|
/**
|
|
30351
30377
|
* Method to manually track agentic events. Uses agent functions for IDs and context.
|
|
30352
|
-
* Automatically applies privacy filters if agentId is provided and agent configuration exists.
|
|
30353
30378
|
*
|
|
30354
30379
|
* @access public
|
|
30355
30380
|
* @category Events
|
|
@@ -30369,64 +30394,8 @@ const privacyFilterCache = new Map();
|
|
|
30369
30394
|
* }, { experiment: 'variant_a' })
|
|
30370
30395
|
*/
|
|
30371
30396
|
function trackAgent(type, props, eventProperties) {
|
|
30372
|
-
const filteredTypes = ['prompt', 'agent_response'];
|
|
30373
|
-
if (_.contains(filteredTypes, type)) {
|
|
30374
|
-
props = applyPrivacyFilters(props);
|
|
30375
|
-
}
|
|
30376
30397
|
collectEventHelper({ type, name: 'agentic', props, eventProperties });
|
|
30377
30398
|
}
|
|
30378
|
-
/**
|
|
30379
|
-
* Apply privacy filters to event properties based on agent configuration
|
|
30380
|
-
* Optimized for maximum performance with caching
|
|
30381
|
-
* @param {Object} props - Event properties to filter
|
|
30382
|
-
* @returns {Object} - Filtered properties
|
|
30383
|
-
*/
|
|
30384
|
-
function applyPrivacyFilters(props) {
|
|
30385
|
-
if (!(props === null || props === void 0 ? void 0 : props.agentId)) {
|
|
30386
|
-
return props;
|
|
30387
|
-
}
|
|
30388
|
-
try {
|
|
30389
|
-
const agents = ConfigReader.get('aiAgents', []);
|
|
30390
|
-
const agent = (agents === null || agents === void 0 ? void 0 : agents.find(agent => agent.id === props.agentId)) || null;
|
|
30391
|
-
const content = props.content;
|
|
30392
|
-
if (!content) {
|
|
30393
|
-
return props;
|
|
30394
|
-
}
|
|
30395
|
-
if (!(agent === null || agent === void 0 ? void 0 : agent.privacyFilters)) {
|
|
30396
|
-
return props;
|
|
30397
|
-
}
|
|
30398
|
-
const privacyFilter = getCachedRegex(agent.privacyFilters);
|
|
30399
|
-
if (!privacyFilter) {
|
|
30400
|
-
return props;
|
|
30401
|
-
}
|
|
30402
|
-
const filteredContent = content.replace(privacyFilter, '<redacted>');
|
|
30403
|
-
if (filteredContent === content) {
|
|
30404
|
-
return props;
|
|
30405
|
-
}
|
|
30406
|
-
return Object.assign(Object.assign({}, props), { content: filteredContent });
|
|
30407
|
-
}
|
|
30408
|
-
catch (error) {
|
|
30409
|
-
console.error('Error applying privacy filters', error);
|
|
30410
|
-
return props;
|
|
30411
|
-
}
|
|
30412
|
-
}
|
|
30413
|
-
/**
|
|
30414
|
-
* Get cached regex or compile and cache new one
|
|
30415
|
-
*/
|
|
30416
|
-
function getCachedRegex(filterPattern) {
|
|
30417
|
-
let regex = privacyFilterCache.get(filterPattern);
|
|
30418
|
-
if (!regex) {
|
|
30419
|
-
try {
|
|
30420
|
-
regex = new RegExp(filterPattern, 'gmi');
|
|
30421
|
-
privacyFilterCache.set(filterPattern, regex);
|
|
30422
|
-
}
|
|
30423
|
-
catch (e) {
|
|
30424
|
-
privacyFilterCache.set(filterPattern, null);
|
|
30425
|
-
return null;
|
|
30426
|
-
}
|
|
30427
|
-
}
|
|
30428
|
-
return regex;
|
|
30429
|
-
}
|
|
30430
30399
|
|
|
30431
30400
|
/**
|
|
30432
30401
|
* Checks visitor and account metadata in the current Pendo installation. Either logs to the console
|
|
@@ -37619,7 +37588,7 @@ const GuideCachePlugin = {
|
|
|
37619
37588
|
if (!capturedEvent || capturedEvent.type !== 'guideSeen') {
|
|
37620
37589
|
return;
|
|
37621
37590
|
}
|
|
37622
|
-
if (capturedEvent.props.reason
|
|
37591
|
+
if (!_.contains(['auto', 'continue'], capturedEvent.props.reason)) {
|
|
37623
37592
|
return;
|
|
37624
37593
|
}
|
|
37625
37594
|
const snap = getSnapshot(capturedEvent.props.guide_id, capturedEvent.props.guide_step_id);
|
|
@@ -40026,7 +39995,7 @@ function SegmentFlags() {
|
|
|
40026
39995
|
}
|
|
40027
39996
|
|
|
40028
39997
|
class DOMPrompt {
|
|
40029
|
-
constructor(pendo, PluginAPI, id,
|
|
39998
|
+
constructor(pendo, PluginAPI, id, inputCssSelectors, submitCssSelectors) {
|
|
40030
39999
|
this.listeners = [];
|
|
40031
40000
|
this.latestPromptValue = '';
|
|
40032
40001
|
this.isActive = true;
|
|
@@ -40037,7 +40006,6 @@ class DOMPrompt {
|
|
|
40037
40006
|
this.api = PluginAPI;
|
|
40038
40007
|
this.q = PluginAPI.q;
|
|
40039
40008
|
this.id = id;
|
|
40040
|
-
this.setFilters(privacyFilters);
|
|
40041
40009
|
if (!this._.isArray(inputCssSelectors)) {
|
|
40042
40010
|
this.api.log.error('inputCssSelectors must be an array, received:', typeof inputCssSelectors);
|
|
40043
40011
|
inputCssSelectors = [];
|
|
@@ -40140,26 +40108,6 @@ class DOMPrompt {
|
|
|
40140
40108
|
const validSubmitEls = this._.filter(this.submitEls, el => el);
|
|
40141
40109
|
this.promptContainer.addObservers(...validInputEls, ...validSubmitEls);
|
|
40142
40110
|
}
|
|
40143
|
-
setFilters(candidateFilter) {
|
|
40144
|
-
if (!candidateFilter) {
|
|
40145
|
-
this.privacyFilters = null;
|
|
40146
|
-
return null;
|
|
40147
|
-
}
|
|
40148
|
-
try {
|
|
40149
|
-
this.privacyFilters = new RegExp(candidateFilter, 'gmi');
|
|
40150
|
-
}
|
|
40151
|
-
catch (e) {
|
|
40152
|
-
this.privacyFilters = null;
|
|
40153
|
-
this.api.log.error(e);
|
|
40154
|
-
}
|
|
40155
|
-
return this.privacyFilters;
|
|
40156
|
-
}
|
|
40157
|
-
applyPrivacyFilter(candidateValue, filters = null) {
|
|
40158
|
-
const filtersToUse = filters || this.privacyFilters;
|
|
40159
|
-
if (!filtersToUse || !this._.isRegExp(filtersToUse))
|
|
40160
|
-
return candidateValue;
|
|
40161
|
-
return candidateValue.replace(filtersToUse, 'redacted');
|
|
40162
|
-
}
|
|
40163
40111
|
submit(origVal) {
|
|
40164
40112
|
if (!origVal || !this.isActive) {
|
|
40165
40113
|
return;
|
|
@@ -40170,11 +40118,9 @@ class DOMPrompt {
|
|
|
40170
40118
|
return;
|
|
40171
40119
|
}
|
|
40172
40120
|
this.lastSubmitTime = now;
|
|
40173
|
-
const val = this.applyPrivacyFilter(origVal);
|
|
40174
40121
|
const payload = {
|
|
40175
40122
|
agentId: this.id,
|
|
40176
|
-
prompt:
|
|
40177
|
-
privacyFilterApplied: val !== origVal
|
|
40123
|
+
prompt: origVal
|
|
40178
40124
|
};
|
|
40179
40125
|
this._.each(this.listeners, (cb) => cb(payload));
|
|
40180
40126
|
}
|
|
@@ -40248,364 +40194,6 @@ class DOMPrompt {
|
|
|
40248
40194
|
}
|
|
40249
40195
|
}
|
|
40250
40196
|
|
|
40251
|
-
const ASSISTANT_MESSAGE = 'assistantMessage';
|
|
40252
|
-
const CONVERSATION_ID_URL_PATTERN = 'conversationIdUrlPattern';
|
|
40253
|
-
const MESSAGE_ID_ATTR = 'messageIdAttr';
|
|
40254
|
-
const MODEL_USED_ATTR = 'modelUsedAttr';
|
|
40255
|
-
const REACTION_ACTIVE = 'reactionActive';
|
|
40256
|
-
const STREAMING_ACTIVE = 'streamingActive';
|
|
40257
|
-
const STREAMING_ACTIVE_ATTR = 'streamingActiveAttr';
|
|
40258
|
-
const THUMB_DOWN = 'thumbDown';
|
|
40259
|
-
const THUMB_UP = 'thumbUp';
|
|
40260
|
-
const TURN_CONTAINER = 'turnContainer';
|
|
40261
|
-
const TURN_ATTR = 'turnAttr';
|
|
40262
|
-
const USER_MESSAGE = 'userMessage';
|
|
40263
|
-
const CUSTOM_AGENT_ID_URL_PATTERN = 'customAgentIdUrlPattern';
|
|
40264
|
-
const CUSTOM_AGENT_NAME = 'customAgentName';
|
|
40265
|
-
const REQUIRED_SELECTORS = [
|
|
40266
|
-
ASSISTANT_MESSAGE,
|
|
40267
|
-
CONVERSATION_ID_URL_PATTERN,
|
|
40268
|
-
MESSAGE_ID_ATTR,
|
|
40269
|
-
STREAMING_ACTIVE,
|
|
40270
|
-
TURN_CONTAINER,
|
|
40271
|
-
TURN_ATTR,
|
|
40272
|
-
USER_MESSAGE
|
|
40273
|
-
];
|
|
40274
|
-
const STREAMING_END_SETTLE_MS = 500;
|
|
40275
|
-
const SUBMISSION_START_RETRY_MS = 1000;
|
|
40276
|
-
const SUBMISSION_START_MAX_ATTEMPTS = 4;
|
|
40277
|
-
class DOMConversation {
|
|
40278
|
-
// Returns the list of required selector types that are missing or empty
|
|
40279
|
-
// in the given cssSelectors config. An empty array means the config is
|
|
40280
|
-
// valid for instantiation.
|
|
40281
|
-
static validateConfig(cssSelectors) {
|
|
40282
|
-
if (!Array.isArray(cssSelectors)) {
|
|
40283
|
-
return REQUIRED_SELECTORS.slice();
|
|
40284
|
-
}
|
|
40285
|
-
const present = new Set();
|
|
40286
|
-
for (const cfg of cssSelectors) {
|
|
40287
|
-
if (!cfg || !cfg.type || !cfg.cssSelector)
|
|
40288
|
-
continue;
|
|
40289
|
-
if (cfg.type === CONVERSATION_ID_URL_PATTERN) {
|
|
40290
|
-
try {
|
|
40291
|
-
RegExp(cfg.cssSelector);
|
|
40292
|
-
}
|
|
40293
|
-
catch (e) {
|
|
40294
|
-
continue;
|
|
40295
|
-
}
|
|
40296
|
-
}
|
|
40297
|
-
present.add(cfg.type);
|
|
40298
|
-
}
|
|
40299
|
-
return REQUIRED_SELECTORS.filter((r) => !present.has(r));
|
|
40300
|
-
}
|
|
40301
|
-
static supportedPreset(preset) {
|
|
40302
|
-
return preset === 'chatgpt-full';
|
|
40303
|
-
}
|
|
40304
|
-
constructor(pendo, PluginAPI, id, privacyFilters, cssSelectors) {
|
|
40305
|
-
this.selectors = {};
|
|
40306
|
-
this.listeners = [];
|
|
40307
|
-
this.isActive = true;
|
|
40308
|
-
this.observers = [];
|
|
40309
|
-
this.wasStreaming = false;
|
|
40310
|
-
this.streamingEndTimer = null;
|
|
40311
|
-
this.submissionStartRetryTimer = null;
|
|
40312
|
-
this.requestNode = null;
|
|
40313
|
-
this.conversationIdRegex = null;
|
|
40314
|
-
this.customAgentIdRegex = null;
|
|
40315
|
-
this.lastReturnedMessageId = null;
|
|
40316
|
-
this.dom = pendo.dom;
|
|
40317
|
-
this._ = pendo._;
|
|
40318
|
-
this.api = PluginAPI;
|
|
40319
|
-
this.Sizzle = pendo.Sizzle;
|
|
40320
|
-
this.id = id;
|
|
40321
|
-
this.setFilters(privacyFilters);
|
|
40322
|
-
if (!this._.isArray(cssSelectors)) {
|
|
40323
|
-
this.api.log.error(`cssSelectors must be an array, received: ${typeof cssSelectors}`);
|
|
40324
|
-
cssSelectors = [];
|
|
40325
|
-
}
|
|
40326
|
-
this._.each(cssSelectors, (cfg) => {
|
|
40327
|
-
if (cfg && cfg.type) {
|
|
40328
|
-
this.selectors[cfg.type] = cfg.cssSelector;
|
|
40329
|
-
}
|
|
40330
|
-
});
|
|
40331
|
-
this.conversationIdRegex = new RegExp(this.selectors[CONVERSATION_ID_URL_PATTERN]);
|
|
40332
|
-
const customAgentIdUrlPattern = this.selectors[CUSTOM_AGENT_ID_URL_PATTERN];
|
|
40333
|
-
if (customAgentIdUrlPattern) { // this is optional
|
|
40334
|
-
try {
|
|
40335
|
-
this.customAgentIdRegex = new RegExp(customAgentIdUrlPattern);
|
|
40336
|
-
}
|
|
40337
|
-
catch (error) {
|
|
40338
|
-
this.api.log.error(`bad customAgentIdUrlPattern ${customAgentIdUrlPattern}`, { error });
|
|
40339
|
-
}
|
|
40340
|
-
}
|
|
40341
|
-
this.root = document.body;
|
|
40342
|
-
this.setupReactionListener();
|
|
40343
|
-
this.setupStreamingObserver();
|
|
40344
|
-
}
|
|
40345
|
-
setupReactionListener() {
|
|
40346
|
-
if (!this.findSelector(THUMB_UP) && !this.findSelector(THUMB_DOWN))
|
|
40347
|
-
return;
|
|
40348
|
-
this.reactionClickHandler = this.handleReactionClick.bind(this);
|
|
40349
|
-
this.root.addEventListener('click', this.reactionClickHandler, true);
|
|
40350
|
-
}
|
|
40351
|
-
handleReactionClick(evt) {
|
|
40352
|
-
const target = evt.target;
|
|
40353
|
-
if (!target || target.nodeType !== 1)
|
|
40354
|
-
return;
|
|
40355
|
-
const upSelector = this.findSelector(THUMB_UP);
|
|
40356
|
-
const downSelector = this.findSelector(THUMB_DOWN);
|
|
40357
|
-
const thumb = this.dom(target).closest(`${upSelector}, ${downSelector}`);
|
|
40358
|
-
if (!thumb.length)
|
|
40359
|
-
return;
|
|
40360
|
-
const isUp = this.Sizzle.matchesSelector(thumb[0], upSelector);
|
|
40361
|
-
const activeSelector = this.findSelector(REACTION_ACTIVE);
|
|
40362
|
-
const wasPressed = activeSelector && this.Sizzle.matchesSelector(thumb[0], activeSelector);
|
|
40363
|
-
const direction = isUp ? 'positive' : 'negative';
|
|
40364
|
-
const content = wasPressed ? 'unreact' : direction;
|
|
40365
|
-
const assistantNode = thumb.closest(this.findSelector(TURN_CONTAINER))
|
|
40366
|
-
.find(this.findSelector(ASSISTANT_MESSAGE));
|
|
40367
|
-
this.emit('user_reaction', {
|
|
40368
|
-
agentId: this.id,
|
|
40369
|
-
messageId: this.getMessageId(assistantNode),
|
|
40370
|
-
content,
|
|
40371
|
-
privacyFilterApplied: false
|
|
40372
|
-
});
|
|
40373
|
-
}
|
|
40374
|
-
// observe the streaming indicator
|
|
40375
|
-
setupStreamingObserver() {
|
|
40376
|
-
this.wasStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
40377
|
-
this.stopStreamingEndTimer();
|
|
40378
|
-
const MutationObserver = getZoneSafeMethod('MutationObserver');
|
|
40379
|
-
const observer = new MutationObserver(this.handleMutation.bind(this));
|
|
40380
|
-
const attrName = this.findSelector(STREAMING_ACTIVE_ATTR);
|
|
40381
|
-
observer.observe(this.root, {
|
|
40382
|
-
childList: true,
|
|
40383
|
-
subtree: true,
|
|
40384
|
-
attributes: true,
|
|
40385
|
-
attributeFilter: attrName ? [attrName] : undefined
|
|
40386
|
-
});
|
|
40387
|
-
this.observers.push(observer);
|
|
40388
|
-
}
|
|
40389
|
-
// look for changes in the state of the streaming indicator.
|
|
40390
|
-
// streaming started -> a prompt has been submitted
|
|
40391
|
-
// streaming ended -> got a response
|
|
40392
|
-
// we don't send the prompt immediately when it's submitted because it might not have a
|
|
40393
|
-
// conversation id yet.
|
|
40394
|
-
// we don't send the agent_response immediately when the streaming indicator is cleared
|
|
40395
|
-
// because it clears slightly before the final text chunk is committed to the DOM so the
|
|
40396
|
-
// end-handling is deferred by a short settle window
|
|
40397
|
-
handleMutation() {
|
|
40398
|
-
const isStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
40399
|
-
if (isStreaming === this.wasStreaming)
|
|
40400
|
-
return;
|
|
40401
|
-
if (isStreaming) { // started streaming
|
|
40402
|
-
if (!this.streamingEndTimer) {
|
|
40403
|
-
this.onSubmissionStart();
|
|
40404
|
-
}
|
|
40405
|
-
else {
|
|
40406
|
-
// stream restarted during the settle window (multi-phase response).
|
|
40407
|
-
// treat as a continuation of the prior request, not a new submission.
|
|
40408
|
-
// we'll handle it next time streaming stops
|
|
40409
|
-
this.stopStreamingEndTimer();
|
|
40410
|
-
}
|
|
40411
|
-
}
|
|
40412
|
-
else { // finished streaming
|
|
40413
|
-
this.startStreamingEndTimer();
|
|
40414
|
-
}
|
|
40415
|
-
this.wasStreaming = isStreaming;
|
|
40416
|
-
}
|
|
40417
|
-
onSubmissionStart(attempt = 1) {
|
|
40418
|
-
this.requestNode = this.findLastRequestNode();
|
|
40419
|
-
if (this.requestNode) {
|
|
40420
|
-
this.handleUserMessage(this.requestNode);
|
|
40421
|
-
return;
|
|
40422
|
-
}
|
|
40423
|
-
// URL hasn't flipped yet to include the conversation id on a fresh chat
|
|
40424
|
-
// retry, if that still fails, onSubmissionEnd handles it
|
|
40425
|
-
if (attempt < SUBMISSION_START_MAX_ATTEMPTS) {
|
|
40426
|
-
this.startSubmissionStartRetryTimer(attempt + 1);
|
|
40427
|
-
}
|
|
40428
|
-
}
|
|
40429
|
-
onSubmissionEnd() {
|
|
40430
|
-
this.stopSubmissionStartRetryTimer();
|
|
40431
|
-
let requestNode = this.requestNode;
|
|
40432
|
-
this.requestNode = null;
|
|
40433
|
-
if (!requestNode) {
|
|
40434
|
-
// onSubmissionStart didn't send, send it now
|
|
40435
|
-
requestNode = this.findLastRequestNode();
|
|
40436
|
-
if (!requestNode)
|
|
40437
|
-
return;
|
|
40438
|
-
this.handleUserMessage(requestNode);
|
|
40439
|
-
}
|
|
40440
|
-
const requestTurn = this.findTurnNumber(requestNode);
|
|
40441
|
-
if (requestTurn === null)
|
|
40442
|
-
return;
|
|
40443
|
-
const assistantNodes = this.select(ASSISTANT_MESSAGE);
|
|
40444
|
-
const toEmit = [];
|
|
40445
|
-
for (let i = assistantNodes.length - 1; i >= 0; i--) {
|
|
40446
|
-
const n = this.findTurnNumber(this.dom(assistantNodes[i]));
|
|
40447
|
-
if (n === null)
|
|
40448
|
-
continue;
|
|
40449
|
-
if (n <= requestTurn)
|
|
40450
|
-
break;
|
|
40451
|
-
toEmit.push(assistantNodes[i]);
|
|
40452
|
-
}
|
|
40453
|
-
// emit in chronological order (reverse of collection order)
|
|
40454
|
-
for (let i = toEmit.length - 1; i >= 0; i--) {
|
|
40455
|
-
this.handleAssistantMessage(this.dom(toEmit[i]));
|
|
40456
|
-
}
|
|
40457
|
-
}
|
|
40458
|
-
extractContent(node) {
|
|
40459
|
-
const rawContent = node.text().trim();
|
|
40460
|
-
const content = this.applyPrivacyFilter(rawContent);
|
|
40461
|
-
return { content, privacyFilterApplied: content !== rawContent };
|
|
40462
|
-
}
|
|
40463
|
-
getMessageId(node) {
|
|
40464
|
-
return node.attr(this.findSelector(MESSAGE_ID_ATTR));
|
|
40465
|
-
}
|
|
40466
|
-
handleUserMessage(node) {
|
|
40467
|
-
this.emit('prompt', Object.assign({ agentId: this.id, messageId: this.getMessageId(node) }, this.extractContent(node)));
|
|
40468
|
-
}
|
|
40469
|
-
handleAssistantMessage(node) {
|
|
40470
|
-
const modelUsedAttr = this.findSelector(MODEL_USED_ATTR);
|
|
40471
|
-
const modelUsed = modelUsedAttr && node.attr(modelUsedAttr);
|
|
40472
|
-
this.emit('agent_response', Object.assign({ agentId: this.id, messageId: this.getMessageId(node), agentModelsUsed: modelUsed ? [modelUsed] : [] }, this.extractContent(node)));
|
|
40473
|
-
}
|
|
40474
|
-
findLastRequestNode() {
|
|
40475
|
-
if (!this.getConversationId())
|
|
40476
|
-
return null;
|
|
40477
|
-
const node = this._.last(this.select(USER_MESSAGE));
|
|
40478
|
-
if (!node)
|
|
40479
|
-
return null;
|
|
40480
|
-
const $node = this.dom(node);
|
|
40481
|
-
const messageId = this.getMessageId($node);
|
|
40482
|
-
if (!messageId)
|
|
40483
|
-
return null;
|
|
40484
|
-
if (messageId === this.lastReturnedMessageId)
|
|
40485
|
-
return null;
|
|
40486
|
-
this.lastReturnedMessageId = messageId;
|
|
40487
|
-
return $node;
|
|
40488
|
-
}
|
|
40489
|
-
findTurnNumber(node) {
|
|
40490
|
-
const turnSelector = this.findSelector(TURN_CONTAINER);
|
|
40491
|
-
const turnAttr = this.findSelector(TURN_ATTR);
|
|
40492
|
-
const turn = node.closest(turnSelector).attr(turnAttr);
|
|
40493
|
-
if (!turn)
|
|
40494
|
-
return null;
|
|
40495
|
-
const seqMatch = String(turn).match(/\d+/);
|
|
40496
|
-
return seqMatch ? parseInt(seqMatch[0], 10) : null;
|
|
40497
|
-
}
|
|
40498
|
-
findSelector(type) {
|
|
40499
|
-
return this.selectors[type];
|
|
40500
|
-
}
|
|
40501
|
-
select(type) {
|
|
40502
|
-
return this.dom(this.findSelector(type), this.root);
|
|
40503
|
-
}
|
|
40504
|
-
getConversationId() {
|
|
40505
|
-
const m = location.href.match(this.conversationIdRegex);
|
|
40506
|
-
if (!m)
|
|
40507
|
-
return null;
|
|
40508
|
-
return m[1] !== undefined ? m[1] : m[0];
|
|
40509
|
-
}
|
|
40510
|
-
getCustomAgentId() {
|
|
40511
|
-
if (!this.customAgentIdRegex)
|
|
40512
|
-
return undefined;
|
|
40513
|
-
const m = location.href.match(this.customAgentIdRegex);
|
|
40514
|
-
if (!m)
|
|
40515
|
-
return undefined;
|
|
40516
|
-
return m[1] !== undefined ? m[1] : m[0];
|
|
40517
|
-
}
|
|
40518
|
-
getCustomAgentName() {
|
|
40519
|
-
if (!this.findSelector(CUSTOM_AGENT_NAME))
|
|
40520
|
-
return '';
|
|
40521
|
-
const node = this.select(CUSTOM_AGENT_NAME);
|
|
40522
|
-
if (!node.length)
|
|
40523
|
-
return '';
|
|
40524
|
-
return node.text().trim();
|
|
40525
|
-
}
|
|
40526
|
-
addCustomAgentInfo(eventPayload) {
|
|
40527
|
-
const customAgentId = this.getCustomAgentId();
|
|
40528
|
-
if (!customAgentId)
|
|
40529
|
-
return;
|
|
40530
|
-
eventPayload.customAgentId = customAgentId;
|
|
40531
|
-
const customAgentName = this.getCustomAgentName();
|
|
40532
|
-
if (customAgentName) {
|
|
40533
|
-
eventPayload.customAgentName = customAgentName;
|
|
40534
|
-
}
|
|
40535
|
-
}
|
|
40536
|
-
setFilters(candidateFilter) {
|
|
40537
|
-
if (!candidateFilter) {
|
|
40538
|
-
this.privacyFilters = null;
|
|
40539
|
-
return null;
|
|
40540
|
-
}
|
|
40541
|
-
try {
|
|
40542
|
-
this.privacyFilters = new RegExp(candidateFilter, 'gmi');
|
|
40543
|
-
}
|
|
40544
|
-
catch (e) {
|
|
40545
|
-
this.privacyFilters = null;
|
|
40546
|
-
this.api.log.error(e);
|
|
40547
|
-
}
|
|
40548
|
-
return this.privacyFilters;
|
|
40549
|
-
}
|
|
40550
|
-
applyPrivacyFilter(candidateValue, filters = null) {
|
|
40551
|
-
const filtersToUse = filters || this.privacyFilters;
|
|
40552
|
-
if (!filtersToUse || !this._.isRegExp(filtersToUse))
|
|
40553
|
-
return candidateValue;
|
|
40554
|
-
return candidateValue.replace(filtersToUse, 'redacted');
|
|
40555
|
-
}
|
|
40556
|
-
onSubmit(callback) {
|
|
40557
|
-
this.listeners.push(callback);
|
|
40558
|
-
}
|
|
40559
|
-
emit(eventType, eventPayload) {
|
|
40560
|
-
eventPayload.conversationId = this.getConversationId();
|
|
40561
|
-
this.addCustomAgentInfo(eventPayload);
|
|
40562
|
-
this._.each(this.listeners, (cb) => cb(eventType, eventPayload));
|
|
40563
|
-
}
|
|
40564
|
-
startStreamingEndTimer() {
|
|
40565
|
-
this.stopStreamingEndTimer();
|
|
40566
|
-
this.streamingEndTimer = setTimeout$1(() => {
|
|
40567
|
-
this.streamingEndTimer = null;
|
|
40568
|
-
this.onSubmissionEnd();
|
|
40569
|
-
}, STREAMING_END_SETTLE_MS);
|
|
40570
|
-
}
|
|
40571
|
-
stopStreamingEndTimer() {
|
|
40572
|
-
if (!this.streamingEndTimer)
|
|
40573
|
-
return;
|
|
40574
|
-
clearTimeout(this.streamingEndTimer);
|
|
40575
|
-
this.streamingEndTimer = null;
|
|
40576
|
-
}
|
|
40577
|
-
startSubmissionStartRetryTimer(attempt) {
|
|
40578
|
-
this.stopSubmissionStartRetryTimer();
|
|
40579
|
-
this.submissionStartRetryTimer = setTimeout$1(() => {
|
|
40580
|
-
this.submissionStartRetryTimer = null;
|
|
40581
|
-
this.onSubmissionStart(attempt);
|
|
40582
|
-
}, SUBMISSION_START_RETRY_MS);
|
|
40583
|
-
}
|
|
40584
|
-
stopSubmissionStartRetryTimer() {
|
|
40585
|
-
if (!this.submissionStartRetryTimer)
|
|
40586
|
-
return;
|
|
40587
|
-
clearTimeout(this.submissionStartRetryTimer);
|
|
40588
|
-
this.submissionStartRetryTimer = null;
|
|
40589
|
-
}
|
|
40590
|
-
suspend() {
|
|
40591
|
-
this.isActive = false;
|
|
40592
|
-
}
|
|
40593
|
-
resume() {
|
|
40594
|
-
this.isActive = true;
|
|
40595
|
-
}
|
|
40596
|
-
teardown() {
|
|
40597
|
-
this._.each(this.observers, (o) => o && o.disconnect && o.disconnect());
|
|
40598
|
-
this.observers = [];
|
|
40599
|
-
if (this.reactionClickHandler && this.root) {
|
|
40600
|
-
this.root.removeEventListener('click', this.reactionClickHandler, true);
|
|
40601
|
-
this.reactionClickHandler = null;
|
|
40602
|
-
}
|
|
40603
|
-
this.stopStreamingEndTimer();
|
|
40604
|
-
this.stopSubmissionStartRetryTimer();
|
|
40605
|
-
this.listeners = [];
|
|
40606
|
-
}
|
|
40607
|
-
}
|
|
40608
|
-
|
|
40609
40197
|
/*
|
|
40610
40198
|
* Prompt Analytics Plugin
|
|
40611
40199
|
*
|
|
@@ -40690,6 +40278,9 @@ class PromptPlugin {
|
|
|
40690
40278
|
});
|
|
40691
40279
|
const currentlyActiveIds = this._.map(this._.filter(this.prompts, prompt => prompt.isActive), prompt => prompt.id);
|
|
40692
40280
|
this._.each(allAgents, aiAgent => {
|
|
40281
|
+
if (this.isConversationAgent(aiAgent)) {
|
|
40282
|
+
return;
|
|
40283
|
+
}
|
|
40693
40284
|
const isCurrentlyActive = this._.contains(currentlyActiveIds, aiAgent.id);
|
|
40694
40285
|
const shouldBeActive = this.testPageRule(aiAgent.pageRule, currentUrl, aiAgent.id);
|
|
40695
40286
|
if (shouldBeActive && !isCurrentlyActive) {
|
|
@@ -40703,10 +40294,9 @@ class PromptPlugin {
|
|
|
40703
40294
|
}
|
|
40704
40295
|
return this.agentsCache.get(agentId);
|
|
40705
40296
|
}
|
|
40706
|
-
|
|
40707
|
-
|
|
40708
|
-
|
|
40709
|
-
return candidateValue.replace(filters, 'redacted');
|
|
40297
|
+
// conversation agents, owned by the ConversationAnalytics plugin.
|
|
40298
|
+
isConversationAgent(agent) {
|
|
40299
|
+
return agent.preset && agent.preset != 'chatgpt';
|
|
40710
40300
|
}
|
|
40711
40301
|
onConfigLoaded(aiAgents = []) {
|
|
40712
40302
|
if (!aiAgents || !this._.isArray(aiAgents) || aiAgents.length === 0) {
|
|
@@ -40715,6 +40305,9 @@ class PromptPlugin {
|
|
|
40715
40305
|
const networkAgents = [];
|
|
40716
40306
|
const domAgents = [];
|
|
40717
40307
|
for (const agent of aiAgents) {
|
|
40308
|
+
if (this.isConversationAgent(agent)) {
|
|
40309
|
+
continue;
|
|
40310
|
+
}
|
|
40718
40311
|
if (agent.trackingMethod === 'network') {
|
|
40719
40312
|
networkAgents.push(agent);
|
|
40720
40313
|
}
|
|
@@ -40751,13 +40344,11 @@ class PromptPlugin {
|
|
|
40751
40344
|
return;
|
|
40752
40345
|
}
|
|
40753
40346
|
const originalBody = body || '';
|
|
40754
|
-
const filteredPrompt = this.applyPrivacyFilter(originalBody, aiAgent.privacyFilters);
|
|
40755
40347
|
this.api.analytics.collectEvent('prompt', {
|
|
40756
|
-
prompt:
|
|
40348
|
+
prompt: originalBody,
|
|
40757
40349
|
agentId: aiAgent.id,
|
|
40758
40350
|
promptType: method,
|
|
40759
|
-
url
|
|
40760
|
-
privacyFilterApplied: filteredPrompt !== originalBody
|
|
40351
|
+
url
|
|
40761
40352
|
}, undefined, 'agentic');
|
|
40762
40353
|
});
|
|
40763
40354
|
}
|
|
@@ -40770,29 +40361,16 @@ class PromptPlugin {
|
|
|
40770
40361
|
}
|
|
40771
40362
|
}
|
|
40772
40363
|
observePrompt(config) {
|
|
40773
|
-
const { id, cssSelectors
|
|
40364
|
+
const { id, cssSelectors } = config;
|
|
40774
40365
|
// Check if agent already exists
|
|
40775
40366
|
const existingPrompt = this._.find(this.prompts, prompt => prompt.id === id);
|
|
40776
40367
|
if (existingPrompt) {
|
|
40777
40368
|
return;
|
|
40778
40369
|
}
|
|
40779
|
-
if (DOMConversation.supportedPreset(preset)) {
|
|
40780
|
-
const missing = DOMConversation.validateConfig(cssSelectors);
|
|
40781
|
-
if (missing.length) {
|
|
40782
|
-
this.api.log.error('aiAgent config missing required selectors', { agentId: id, missing });
|
|
40783
|
-
return;
|
|
40784
|
-
}
|
|
40785
|
-
const conversation = new DOMConversation(this.pendo, this.api, id, privacyFilters, cssSelectors);
|
|
40786
|
-
conversation.onSubmit((eventType, payload) => {
|
|
40787
|
-
this.sendConversationEvent(eventType, payload);
|
|
40788
|
-
});
|
|
40789
|
-
this.prompts.push(conversation);
|
|
40790
|
-
return;
|
|
40791
|
-
}
|
|
40792
40370
|
let inputCssSelectors, submitCssSelectors;
|
|
40793
40371
|
inputCssSelectors = this.parseCssSelector('input', cssSelectors);
|
|
40794
40372
|
submitCssSelectors = this.parseCssSelector('submit', cssSelectors);
|
|
40795
|
-
const prompt = new DOMPrompt(this.pendo, this.api, id,
|
|
40373
|
+
const prompt = new DOMPrompt(this.pendo, this.api, id, inputCssSelectors, submitCssSelectors);
|
|
40796
40374
|
prompt.onSubmit((promptPayload) => {
|
|
40797
40375
|
this.sendPromptEvent(promptPayload);
|
|
40798
40376
|
});
|
|
@@ -40815,15 +40393,6 @@ class PromptPlugin {
|
|
|
40815
40393
|
}, promptEvent);
|
|
40816
40394
|
this.api.analytics.collectEvent('prompt', event, undefined, 'agentic');
|
|
40817
40395
|
}
|
|
40818
|
-
sendConversationEvent(eventType, eventPayload) {
|
|
40819
|
-
// Block event if the prompt is suspended
|
|
40820
|
-
const conv = this._.find(this.prompts, p => p.id === eventPayload.agentId);
|
|
40821
|
-
if (conv && !conv.isActive) {
|
|
40822
|
-
return;
|
|
40823
|
-
}
|
|
40824
|
-
eventPayload = this._.extend({ agentType: 'conversation' }, eventPayload);
|
|
40825
|
-
this.api.analytics.collectEvent(eventType, eventPayload, undefined, 'agentic');
|
|
40826
|
-
}
|
|
40827
40396
|
teardown() {
|
|
40828
40397
|
this._.each(this.prompts, (prompt) => prompt.teardown());
|
|
40829
40398
|
if (this.agentsCache) {
|
|
@@ -41210,10 +40779,7 @@ class WebAnalytics {
|
|
|
41210
40779
|
PluginAPI.attachEvent(PluginAPI.Events, 'eventCaptured', bind(this.addUtmToEvent, this)),
|
|
41211
40780
|
PluginAPI.attachEvent(PluginAPI.Events, 'sessionChanged', bind(this.sessionChanged, this))
|
|
41212
40781
|
];
|
|
41213
|
-
|
|
41214
|
-
if (!params || pendo._.isEmpty(params)) {
|
|
41215
|
-
params = this.extractParameters(window.location.href, document.referrer);
|
|
41216
|
-
}
|
|
40782
|
+
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
41217
40783
|
/**
|
|
41218
40784
|
* Stores UTM parameters that were present in the URL when the visitor loaded the application.
|
|
41219
40785
|
*
|
|
@@ -41222,9 +40788,12 @@ class WebAnalytics {
|
|
|
41222
40788
|
* @access public
|
|
41223
40789
|
* @label WEB_ANALYTICS_STORAGE_KEY
|
|
41224
40790
|
*/
|
|
41225
|
-
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY);
|
|
40791
|
+
this.api.agentStorage.registry.addLocal(WEB_ANALYTICS_STORAGE_KEY, { cookieSuffix: this.suffix, isSecure: true });
|
|
40792
|
+
let params = this.loadParameters(PluginAPI.agentStorage);
|
|
40793
|
+
if (!params || pendo._.isEmpty(params)) {
|
|
40794
|
+
params = this.extractParameters(window.location.href, document.referrer);
|
|
40795
|
+
}
|
|
41226
40796
|
this.storeParameters(params, PluginAPI.agentStorage);
|
|
41227
|
-
this.suffix = this.api.ConfigReader.get('identityStorageSuffix');
|
|
41228
40797
|
}
|
|
41229
40798
|
extractParameters(url, referrer) {
|
|
41230
40799
|
const locationUrl = new URL(url);
|
|
@@ -41279,12 +40848,12 @@ class WebAnalytics {
|
|
|
41279
40848
|
}
|
|
41280
40849
|
storeParameters(params, storage) {
|
|
41281
40850
|
if (this.pendo._.size(params) > 0) {
|
|
41282
|
-
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params)
|
|
40851
|
+
storage.write(WEB_ANALYTICS_STORAGE_KEY, JSON.stringify(params));
|
|
41283
40852
|
}
|
|
41284
40853
|
}
|
|
41285
40854
|
loadParameters(storage) {
|
|
41286
|
-
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY
|
|
41287
|
-
storage.read(WEB_ANALYTICS_STORAGE_KEY);
|
|
40855
|
+
const storedParamsJson = storage.read(WEB_ANALYTICS_STORAGE_KEY) ||
|
|
40856
|
+
storage.read(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
41288
40857
|
if (storedParamsJson)
|
|
41289
40858
|
return JSON.parse(storedParamsJson);
|
|
41290
40859
|
return null;
|
|
@@ -41310,7 +40879,7 @@ class WebAnalytics {
|
|
|
41310
40879
|
sessionChanged() {
|
|
41311
40880
|
const agentStorage = this.api.agentStorage;
|
|
41312
40881
|
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY);
|
|
41313
|
-
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false,
|
|
40882
|
+
agentStorage.clear(WEB_ANALYTICS_STORAGE_KEY, false, '');
|
|
41314
40883
|
const params = this.extractParameters(window.location.href, document.referrer);
|
|
41315
40884
|
this.storeParameters(params, agentStorage);
|
|
41316
40885
|
}
|
|
@@ -41805,6 +41374,21 @@ function branchingGoToStep(event, step, guide, pendo) {
|
|
|
41805
41374
|
event.cancel = true;
|
|
41806
41375
|
}
|
|
41807
41376
|
|
|
41377
|
+
var DANGEROUS_URL_SCHEME = /^\s*(javascript|data|vbscript):/i;
|
|
41378
|
+
function encodeMetadataForUrl(value, urlBeforePlaceholder) {
|
|
41379
|
+
if (urlBeforePlaceholder === void 0) { urlBeforePlaceholder = ''; }
|
|
41380
|
+
var str = value === undefined || value === null ? '' : String(value);
|
|
41381
|
+
var inUrlComponent = urlBeforePlaceholder.indexOf('?') !== -1 || urlBeforePlaceholder.indexOf('#') !== -1;
|
|
41382
|
+
if (inUrlComponent) {
|
|
41383
|
+
return window.encodeURIComponent(str);
|
|
41384
|
+
}
|
|
41385
|
+
var schemeAlreadySet = urlBeforePlaceholder.indexOf(':') !== -1;
|
|
41386
|
+
if (!schemeAlreadySet && DANGEROUS_URL_SCHEME.test(str)) {
|
|
41387
|
+
return '';
|
|
41388
|
+
}
|
|
41389
|
+
return window.encodeURI(str);
|
|
41390
|
+
}
|
|
41391
|
+
|
|
41808
41392
|
const MetadataSubstitution = {
|
|
41809
41393
|
name: 'MetadataSubstitution',
|
|
41810
41394
|
script(step, guide, pendo) {
|
|
@@ -41948,8 +41532,10 @@ function substituteMetadataByTarget(data, target, mdValue, matched) {
|
|
|
41948
41532
|
? (data.getAttribute(target) || '')
|
|
41949
41533
|
: data[target];
|
|
41950
41534
|
if (target === 'href' || target === 'value') {
|
|
41951
|
-
const
|
|
41952
|
-
|
|
41535
|
+
const decodedUrl = decodeURI(current);
|
|
41536
|
+
const urlBeforePlaceholder = decodedUrl.slice(0, decodedUrl.indexOf(matched[0]));
|
|
41537
|
+
const safeValue = encodeMetadataForUrl(mdValue, urlBeforePlaceholder);
|
|
41538
|
+
data[target] = decodedUrl.replace(matched[0], safeValue);
|
|
41953
41539
|
}
|
|
41954
41540
|
else {
|
|
41955
41541
|
data[target] = current.replace(matched[0], mdValue);
|
|
@@ -53384,4 +52970,478 @@ const PredictGuides = () => {
|
|
|
53384
52970
|
};
|
|
53385
52971
|
};
|
|
53386
52972
|
|
|
53387
|
-
|
|
52973
|
+
const ASSISTANT_MESSAGE = 'assistantMessage';
|
|
52974
|
+
const CONVERSATION_ID_URL_PATTERN = 'conversationIdUrlPattern';
|
|
52975
|
+
const MESSAGE_ID_ATTR = 'messageIdAttr';
|
|
52976
|
+
const MESSAGE_ID_PATTERN = 'messageIdPattern';
|
|
52977
|
+
const MODEL_USED_ATTR = 'modelUsedAttr';
|
|
52978
|
+
const REACTION_ACTIVE = 'reactionActive';
|
|
52979
|
+
const STREAMING_ACTIVE = 'streamingActive';
|
|
52980
|
+
const STREAMING_ACTIVE_ATTR = 'streamingActiveAttr';
|
|
52981
|
+
const THUMB_DOWN = 'thumbDown';
|
|
52982
|
+
const THUMB_UP = 'thumbUp';
|
|
52983
|
+
const TURN_CONTAINER = 'turnContainer';
|
|
52984
|
+
const USER_MESSAGE = 'userMessage';
|
|
52985
|
+
const CUSTOM_AGENT_ID_URL_PATTERN = 'customAgentIdUrlPattern';
|
|
52986
|
+
const CUSTOM_AGENT_NAME = 'customAgentName';
|
|
52987
|
+
const REQUIRED_SELECTORS = [
|
|
52988
|
+
ASSISTANT_MESSAGE,
|
|
52989
|
+
CONVERSATION_ID_URL_PATTERN,
|
|
52990
|
+
MESSAGE_ID_ATTR,
|
|
52991
|
+
STREAMING_ACTIVE,
|
|
52992
|
+
TURN_CONTAINER,
|
|
52993
|
+
USER_MESSAGE
|
|
52994
|
+
];
|
|
52995
|
+
const SUPPORTED_PRESETS = ['chatgpt-full', 'gemini-full'];
|
|
52996
|
+
const STREAMING_END_SETTLE_MS = 500;
|
|
52997
|
+
const SUBMISSION_START_RETRY_MS = 1000;
|
|
52998
|
+
const SUBMISSION_START_MAX_ATTEMPTS = 4;
|
|
52999
|
+
class DOMConversation {
|
|
53000
|
+
// Returns the list of required selector types that are missing or empty
|
|
53001
|
+
// in the given cssSelectors config. An empty array means the config is
|
|
53002
|
+
// valid for instantiation.
|
|
53003
|
+
static validateConfig(cssSelectors) {
|
|
53004
|
+
if (!Array.isArray(cssSelectors)) {
|
|
53005
|
+
return REQUIRED_SELECTORS.slice();
|
|
53006
|
+
}
|
|
53007
|
+
const present = new Set();
|
|
53008
|
+
for (const cfg of cssSelectors) {
|
|
53009
|
+
if (!cfg || !cfg.type || !cfg.cssSelector)
|
|
53010
|
+
continue;
|
|
53011
|
+
if (cfg.type === CONVERSATION_ID_URL_PATTERN) {
|
|
53012
|
+
try {
|
|
53013
|
+
RegExp(cfg.cssSelector);
|
|
53014
|
+
}
|
|
53015
|
+
catch (e) {
|
|
53016
|
+
continue;
|
|
53017
|
+
}
|
|
53018
|
+
}
|
|
53019
|
+
present.add(cfg.type);
|
|
53020
|
+
}
|
|
53021
|
+
return REQUIRED_SELECTORS.filter((r) => !present.has(r));
|
|
53022
|
+
}
|
|
53023
|
+
static supportedPreset(preset) {
|
|
53024
|
+
return SUPPORTED_PRESETS.indexOf(preset) !== -1;
|
|
53025
|
+
}
|
|
53026
|
+
constructor(pendo, PluginAPI, id, cssSelectors) {
|
|
53027
|
+
this.selectors = {};
|
|
53028
|
+
this.listeners = [];
|
|
53029
|
+
this.isActive = true;
|
|
53030
|
+
this.observers = [];
|
|
53031
|
+
this.wasStreaming = false;
|
|
53032
|
+
this.streamingEndTimer = null;
|
|
53033
|
+
this.submissionStartRetryTimer = null;
|
|
53034
|
+
this.requestNode = null;
|
|
53035
|
+
this.conversationIdRegex = null;
|
|
53036
|
+
this.customAgentIdRegex = null;
|
|
53037
|
+
this.messageIdRegex = null;
|
|
53038
|
+
this.lastReturnedMessageId = null;
|
|
53039
|
+
this.dom = pendo.dom;
|
|
53040
|
+
this._ = pendo._;
|
|
53041
|
+
this.api = PluginAPI;
|
|
53042
|
+
this.Sizzle = pendo.Sizzle;
|
|
53043
|
+
this.id = id;
|
|
53044
|
+
if (!this._.isArray(cssSelectors)) {
|
|
53045
|
+
this.api.log.error(`cssSelectors must be an array, received: ${typeof cssSelectors}`);
|
|
53046
|
+
cssSelectors = [];
|
|
53047
|
+
}
|
|
53048
|
+
this._.each(cssSelectors, (cfg) => {
|
|
53049
|
+
if (cfg && cfg.type) {
|
|
53050
|
+
this.selectors[cfg.type] = cfg.cssSelector;
|
|
53051
|
+
}
|
|
53052
|
+
});
|
|
53053
|
+
this.conversationIdRegex = new RegExp(this.selectors[CONVERSATION_ID_URL_PATTERN]);
|
|
53054
|
+
const customAgentIdUrlPattern = this.selectors[CUSTOM_AGENT_ID_URL_PATTERN];
|
|
53055
|
+
if (customAgentIdUrlPattern) { // this is optional
|
|
53056
|
+
try {
|
|
53057
|
+
this.customAgentIdRegex = new RegExp(customAgentIdUrlPattern);
|
|
53058
|
+
}
|
|
53059
|
+
catch (error) {
|
|
53060
|
+
this.api.log.error(`bad customAgentIdUrlPattern ${customAgentIdUrlPattern}`, { error });
|
|
53061
|
+
}
|
|
53062
|
+
}
|
|
53063
|
+
const messageIdPattern = this.selectors[MESSAGE_ID_PATTERN];
|
|
53064
|
+
if (messageIdPattern) { // optional: gates the request node until its id is fully rendered
|
|
53065
|
+
try {
|
|
53066
|
+
this.messageIdRegex = new RegExp(messageIdPattern);
|
|
53067
|
+
}
|
|
53068
|
+
catch (error) {
|
|
53069
|
+
this.api.log.error(`bad messageIdPattern ${messageIdPattern}`, { error });
|
|
53070
|
+
}
|
|
53071
|
+
}
|
|
53072
|
+
this.root = document.body;
|
|
53073
|
+
this.setupReactionListener();
|
|
53074
|
+
this.setupStreamingObserver();
|
|
53075
|
+
}
|
|
53076
|
+
setupReactionListener() {
|
|
53077
|
+
if (!this.findSelector(THUMB_UP) && !this.findSelector(THUMB_DOWN))
|
|
53078
|
+
return;
|
|
53079
|
+
this.reactionClickHandler = this.handleReactionClick.bind(this);
|
|
53080
|
+
this.root.addEventListener('click', this.reactionClickHandler, true);
|
|
53081
|
+
}
|
|
53082
|
+
handleReactionClick(evt) {
|
|
53083
|
+
const target = evt.target;
|
|
53084
|
+
if (!target || target.nodeType !== 1)
|
|
53085
|
+
return;
|
|
53086
|
+
const upSelector = this.findSelector(THUMB_UP);
|
|
53087
|
+
const downSelector = this.findSelector(THUMB_DOWN);
|
|
53088
|
+
const thumb = this.dom(target).closest(`${upSelector}, ${downSelector}`);
|
|
53089
|
+
if (!thumb.length)
|
|
53090
|
+
return;
|
|
53091
|
+
const isUp = this.Sizzle.matchesSelector(thumb[0], upSelector);
|
|
53092
|
+
const activeSelector = this.findSelector(REACTION_ACTIVE);
|
|
53093
|
+
const wasPressed = activeSelector && this.Sizzle.matchesSelector(thumb[0], activeSelector);
|
|
53094
|
+
const direction = isUp ? 'positive' : 'negative';
|
|
53095
|
+
const content = wasPressed ? 'unreact' : direction;
|
|
53096
|
+
const assistantNode = thumb.closest(this.findSelector(TURN_CONTAINER))
|
|
53097
|
+
.find(this.findSelector(ASSISTANT_MESSAGE));
|
|
53098
|
+
this.emit('user_reaction', {
|
|
53099
|
+
agentId: this.id,
|
|
53100
|
+
messageId: this.getMessageId(assistantNode),
|
|
53101
|
+
content
|
|
53102
|
+
});
|
|
53103
|
+
}
|
|
53104
|
+
// observe the streaming indicator
|
|
53105
|
+
setupStreamingObserver() {
|
|
53106
|
+
this.wasStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
53107
|
+
this.stopStreamingEndTimer();
|
|
53108
|
+
const MutationObserver = getZoneSafeMethod(this._, 'MutationObserver');
|
|
53109
|
+
const observer = new MutationObserver(this.handleMutation.bind(this));
|
|
53110
|
+
const attrName = this.findSelector(STREAMING_ACTIVE_ATTR);
|
|
53111
|
+
observer.observe(this.root, {
|
|
53112
|
+
childList: true,
|
|
53113
|
+
subtree: true,
|
|
53114
|
+
attributes: true,
|
|
53115
|
+
attributeFilter: attrName ? [attrName] : undefined
|
|
53116
|
+
});
|
|
53117
|
+
this.observers.push(observer);
|
|
53118
|
+
}
|
|
53119
|
+
// look for changes in the state of the streaming indicator.
|
|
53120
|
+
// streaming started -> a prompt has been submitted
|
|
53121
|
+
// streaming ended -> got a response
|
|
53122
|
+
// we don't send the prompt immediately when it's submitted because it might not have a
|
|
53123
|
+
// conversation id yet.
|
|
53124
|
+
// we don't send the agent_response immediately when the streaming indicator is cleared
|
|
53125
|
+
// because it clears slightly before the final text chunk is committed to the DOM so the
|
|
53126
|
+
// end-handling is deferred by a short settle window
|
|
53127
|
+
handleMutation() {
|
|
53128
|
+
const isStreaming = this.select(STREAMING_ACTIVE).length > 0;
|
|
53129
|
+
if (isStreaming === this.wasStreaming)
|
|
53130
|
+
return;
|
|
53131
|
+
if (isStreaming) { // started streaming
|
|
53132
|
+
if (!this.streamingEndTimer) {
|
|
53133
|
+
this.onSubmissionStart();
|
|
53134
|
+
}
|
|
53135
|
+
else {
|
|
53136
|
+
// stream restarted during the settle window (multi-phase response).
|
|
53137
|
+
// treat as a continuation of the prior request, not a new submission.
|
|
53138
|
+
// we'll handle it next time streaming stops
|
|
53139
|
+
this.stopStreamingEndTimer();
|
|
53140
|
+
}
|
|
53141
|
+
}
|
|
53142
|
+
else { // finished streaming
|
|
53143
|
+
this.startStreamingEndTimer();
|
|
53144
|
+
}
|
|
53145
|
+
this.wasStreaming = isStreaming;
|
|
53146
|
+
}
|
|
53147
|
+
onSubmissionStart(attempt = 1) {
|
|
53148
|
+
this.requestNode = this.findLastRequestNode();
|
|
53149
|
+
if (this.requestNode) {
|
|
53150
|
+
this.handleUserMessage(this.requestNode);
|
|
53151
|
+
return;
|
|
53152
|
+
}
|
|
53153
|
+
// URL hasn't flipped yet to include the conversation id on a fresh chat
|
|
53154
|
+
// retry, if that still fails, onSubmissionEnd handles it
|
|
53155
|
+
if (attempt < SUBMISSION_START_MAX_ATTEMPTS) {
|
|
53156
|
+
this.startSubmissionStartRetryTimer(attempt + 1);
|
|
53157
|
+
}
|
|
53158
|
+
}
|
|
53159
|
+
onSubmissionEnd() {
|
|
53160
|
+
this.stopSubmissionStartRetryTimer();
|
|
53161
|
+
let requestNode = this.requestNode;
|
|
53162
|
+
this.requestNode = null;
|
|
53163
|
+
if (!requestNode) {
|
|
53164
|
+
// onSubmissionStart didn't send, send it now
|
|
53165
|
+
requestNode = this.findLastRequestNode();
|
|
53166
|
+
if (!requestNode)
|
|
53167
|
+
return;
|
|
53168
|
+
this.handleUserMessage(requestNode);
|
|
53169
|
+
}
|
|
53170
|
+
const requestEl = requestNode[0];
|
|
53171
|
+
if (!requestEl)
|
|
53172
|
+
return;
|
|
53173
|
+
// the request is the latest user message, so any assistant message that
|
|
53174
|
+
// follows it in the DOM is a response to it. document order is already
|
|
53175
|
+
// chronological, so we emit as we go
|
|
53176
|
+
const assistantNodes = this.select(ASSISTANT_MESSAGE);
|
|
53177
|
+
for (let i = 0; i < assistantNodes.length; i++) {
|
|
53178
|
+
const el = assistantNodes[i];
|
|
53179
|
+
if (requestEl.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
53180
|
+
this.handleAssistantMessage(this.dom(el));
|
|
53181
|
+
}
|
|
53182
|
+
}
|
|
53183
|
+
}
|
|
53184
|
+
extractContent(node) {
|
|
53185
|
+
return { content: node.text().trim() };
|
|
53186
|
+
}
|
|
53187
|
+
getMessageId(node) {
|
|
53188
|
+
return node.attr(this.findSelector(MESSAGE_ID_ATTR));
|
|
53189
|
+
}
|
|
53190
|
+
handleUserMessage(node) {
|
|
53191
|
+
this.emit('prompt', Object.assign({ agentId: this.id, messageId: this.getMessageId(node) }, this.extractContent(node)));
|
|
53192
|
+
}
|
|
53193
|
+
handleAssistantMessage(node) {
|
|
53194
|
+
const modelUsedAttr = this.findSelector(MODEL_USED_ATTR);
|
|
53195
|
+
const modelUsed = modelUsedAttr && node.attr(modelUsedAttr);
|
|
53196
|
+
this.emit('agent_response', Object.assign({ agentId: this.id, messageId: this.getMessageId(node), agentModelsUsed: modelUsed ? [modelUsed] : [] }, this.extractContent(node)));
|
|
53197
|
+
}
|
|
53198
|
+
findLastRequestNode() {
|
|
53199
|
+
if (!this.getConversationId())
|
|
53200
|
+
return null;
|
|
53201
|
+
const node = this._.last(this.select(USER_MESSAGE));
|
|
53202
|
+
if (!node)
|
|
53203
|
+
return null;
|
|
53204
|
+
const $node = this.dom(node);
|
|
53205
|
+
const messageId = this.getMessageId($node);
|
|
53206
|
+
if (!messageId)
|
|
53207
|
+
return null;
|
|
53208
|
+
// the node may still be rendering (e.g. a framework interpolating an
|
|
53209
|
+
// unresolved id); treat it as not-ready so the retry waits for the
|
|
53210
|
+
// finalized node rather than caching a placeholder that gets replaced
|
|
53211
|
+
if (this.messageIdRegex && !this.messageIdRegex.test(messageId))
|
|
53212
|
+
return null;
|
|
53213
|
+
if (messageId === this.lastReturnedMessageId)
|
|
53214
|
+
return null;
|
|
53215
|
+
this.lastReturnedMessageId = messageId;
|
|
53216
|
+
return $node;
|
|
53217
|
+
}
|
|
53218
|
+
findSelector(type) {
|
|
53219
|
+
return this.selectors[type];
|
|
53220
|
+
}
|
|
53221
|
+
select(type) {
|
|
53222
|
+
return this.dom(this.findSelector(type), this.root);
|
|
53223
|
+
}
|
|
53224
|
+
getConversationId() {
|
|
53225
|
+
const m = location.href.match(this.conversationIdRegex);
|
|
53226
|
+
if (!m)
|
|
53227
|
+
return null;
|
|
53228
|
+
return m[1] !== undefined ? m[1] : m[0];
|
|
53229
|
+
}
|
|
53230
|
+
getCustomAgentId() {
|
|
53231
|
+
if (!this.customAgentIdRegex)
|
|
53232
|
+
return undefined;
|
|
53233
|
+
const m = location.href.match(this.customAgentIdRegex);
|
|
53234
|
+
if (!m)
|
|
53235
|
+
return undefined;
|
|
53236
|
+
return m[1] !== undefined ? m[1] : m[0];
|
|
53237
|
+
}
|
|
53238
|
+
getCustomAgentName() {
|
|
53239
|
+
if (!this.findSelector(CUSTOM_AGENT_NAME))
|
|
53240
|
+
return '';
|
|
53241
|
+
const node = this.select(CUSTOM_AGENT_NAME);
|
|
53242
|
+
if (!node.length)
|
|
53243
|
+
return '';
|
|
53244
|
+
return node.text().trim();
|
|
53245
|
+
}
|
|
53246
|
+
addCustomAgentInfo(eventPayload) {
|
|
53247
|
+
const customAgentId = this.getCustomAgentId();
|
|
53248
|
+
if (!customAgentId)
|
|
53249
|
+
return;
|
|
53250
|
+
eventPayload.customAgentId = customAgentId;
|
|
53251
|
+
const customAgentName = this.getCustomAgentName();
|
|
53252
|
+
if (customAgentName) {
|
|
53253
|
+
eventPayload.customAgentName = customAgentName;
|
|
53254
|
+
}
|
|
53255
|
+
}
|
|
53256
|
+
onSubmit(callback) {
|
|
53257
|
+
this.listeners.push(callback);
|
|
53258
|
+
}
|
|
53259
|
+
emit(eventType, eventPayload) {
|
|
53260
|
+
eventPayload.conversationId = this.getConversationId();
|
|
53261
|
+
this.addCustomAgentInfo(eventPayload);
|
|
53262
|
+
this._.each(this.listeners, (cb) => cb(eventType, eventPayload));
|
|
53263
|
+
}
|
|
53264
|
+
startStreamingEndTimer() {
|
|
53265
|
+
this.stopStreamingEndTimer();
|
|
53266
|
+
this.streamingEndTimer = setTimeout$1(() => {
|
|
53267
|
+
this.streamingEndTimer = null;
|
|
53268
|
+
this.onSubmissionEnd();
|
|
53269
|
+
}, STREAMING_END_SETTLE_MS);
|
|
53270
|
+
}
|
|
53271
|
+
stopStreamingEndTimer() {
|
|
53272
|
+
if (!this.streamingEndTimer)
|
|
53273
|
+
return;
|
|
53274
|
+
clearTimeout(this.streamingEndTimer);
|
|
53275
|
+
this.streamingEndTimer = null;
|
|
53276
|
+
}
|
|
53277
|
+
startSubmissionStartRetryTimer(attempt) {
|
|
53278
|
+
this.stopSubmissionStartRetryTimer();
|
|
53279
|
+
this.submissionStartRetryTimer = setTimeout$1(() => {
|
|
53280
|
+
this.submissionStartRetryTimer = null;
|
|
53281
|
+
this.onSubmissionStart(attempt);
|
|
53282
|
+
}, SUBMISSION_START_RETRY_MS);
|
|
53283
|
+
}
|
|
53284
|
+
stopSubmissionStartRetryTimer() {
|
|
53285
|
+
if (!this.submissionStartRetryTimer)
|
|
53286
|
+
return;
|
|
53287
|
+
clearTimeout(this.submissionStartRetryTimer);
|
|
53288
|
+
this.submissionStartRetryTimer = null;
|
|
53289
|
+
}
|
|
53290
|
+
suspend() {
|
|
53291
|
+
this.isActive = false;
|
|
53292
|
+
}
|
|
53293
|
+
resume() {
|
|
53294
|
+
this.isActive = true;
|
|
53295
|
+
}
|
|
53296
|
+
teardown() {
|
|
53297
|
+
this._.each(this.observers, (o) => o && o.disconnect && o.disconnect());
|
|
53298
|
+
this.observers = [];
|
|
53299
|
+
if (this.reactionClickHandler && this.root) {
|
|
53300
|
+
this.root.removeEventListener('click', this.reactionClickHandler, true);
|
|
53301
|
+
this.reactionClickHandler = null;
|
|
53302
|
+
}
|
|
53303
|
+
this.stopStreamingEndTimer();
|
|
53304
|
+
this.stopSubmissionStartRetryTimer();
|
|
53305
|
+
this.listeners = [];
|
|
53306
|
+
}
|
|
53307
|
+
}
|
|
53308
|
+
|
|
53309
|
+
/*
|
|
53310
|
+
* Conversation Analytics Plugin
|
|
53311
|
+
*
|
|
53312
|
+
* Independent plugin that tracks AI-agent conversations rendered in the DOM.
|
|
53313
|
+
*
|
|
53314
|
+
* The sibling built-in PromptAnalytics plugin owns the network + DOMPrompt AI-agents
|
|
53315
|
+
*/
|
|
53316
|
+
function ConversationAnalytics() {
|
|
53317
|
+
let pendo;
|
|
53318
|
+
let api;
|
|
53319
|
+
let _;
|
|
53320
|
+
let log;
|
|
53321
|
+
let conversations = [];
|
|
53322
|
+
let agentsCache = new Map();
|
|
53323
|
+
const CONFIG_NAME = 'aiAgents';
|
|
53324
|
+
return {
|
|
53325
|
+
name: 'ConversationAnalytics',
|
|
53326
|
+
initialize,
|
|
53327
|
+
teardown,
|
|
53328
|
+
onConfigLoaded,
|
|
53329
|
+
observeConversation,
|
|
53330
|
+
sendConversationEvent,
|
|
53331
|
+
reEvaluatePageRules
|
|
53332
|
+
};
|
|
53333
|
+
function initialize(pendoGlobal, PluginAPI) {
|
|
53334
|
+
var _a;
|
|
53335
|
+
pendo = pendoGlobal;
|
|
53336
|
+
api = PluginAPI;
|
|
53337
|
+
_ = pendo._;
|
|
53338
|
+
log = api.log;
|
|
53339
|
+
const { ConfigReader } = api;
|
|
53340
|
+
ConfigReader.addOption(CONFIG_NAME, [
|
|
53341
|
+
ConfigReader.sources.PENDO_CONFIG_SRC,
|
|
53342
|
+
ConfigReader.sources.SNIPPET_SRC
|
|
53343
|
+
]);
|
|
53344
|
+
onConfigLoaded(ConfigReader.get(CONFIG_NAME));
|
|
53345
|
+
// Listen for URL changes to re-evaluate page rules
|
|
53346
|
+
// Listening on guidesLoaded since normalizedUrl comes back from the guides playload
|
|
53347
|
+
(_a = api.Events.guidesLoaded) === null || _a === void 0 ? void 0 : _a.on(reEvaluatePageRules);
|
|
53348
|
+
}
|
|
53349
|
+
// Test if the current URL matches the page rule for an agent. The raw matcher lives in core
|
|
53350
|
+
// and is reached through PluginAPI (independent plugins cannot import it directly); it falls
|
|
53351
|
+
// back to the normalized URL internally when no URL is passed.
|
|
53352
|
+
function testPageRule(pageRule, agentId = 'unknown') {
|
|
53353
|
+
// Convert //*/agent pattern to work with full URLs
|
|
53354
|
+
let fixedPageRule = pageRule;
|
|
53355
|
+
if (Array.isArray(pageRule)) {
|
|
53356
|
+
const validRules = _.filter(pageRule, rule => rule && rule.trim() !== '');
|
|
53357
|
+
fixedPageRule = _.map(validRules, rule => rule.startsWith('//*/') ? rule.substring(1) : rule);
|
|
53358
|
+
}
|
|
53359
|
+
else if (typeof pageRule === 'string' && pageRule.startsWith('//*/')) {
|
|
53360
|
+
fixedPageRule = pageRule.substring(1);
|
|
53361
|
+
}
|
|
53362
|
+
return api.util.testPageRule(fixedPageRule, undefined, {
|
|
53363
|
+
logger: log,
|
|
53364
|
+
context: 'agent',
|
|
53365
|
+
id: agentId
|
|
53366
|
+
});
|
|
53367
|
+
}
|
|
53368
|
+
function reEvaluatePageRules() {
|
|
53369
|
+
_.each(conversations, conversation => {
|
|
53370
|
+
const agent = findAgentById(conversation.id);
|
|
53371
|
+
const shouldBeActive = agent && testPageRule(agent.pageRule, agent.id);
|
|
53372
|
+
if (shouldBeActive) {
|
|
53373
|
+
conversation.resume();
|
|
53374
|
+
}
|
|
53375
|
+
else {
|
|
53376
|
+
conversation.suspend();
|
|
53377
|
+
}
|
|
53378
|
+
});
|
|
53379
|
+
const currentlyActiveIds = _.map(_.filter(conversations, conversation => conversation.isActive), conversation => conversation.id);
|
|
53380
|
+
const allAgents = api.ConfigReader.get(CONFIG_NAME) || [];
|
|
53381
|
+
_.each(allAgents, aiAgent => {
|
|
53382
|
+
if (!isConversationAgent(aiAgent)) {
|
|
53383
|
+
return;
|
|
53384
|
+
}
|
|
53385
|
+
const isCurrentlyActive = _.contains(currentlyActiveIds, aiAgent.id);
|
|
53386
|
+
const shouldBeActive = testPageRule(aiAgent.pageRule, aiAgent.id);
|
|
53387
|
+
if (shouldBeActive && !isCurrentlyActive) {
|
|
53388
|
+
observeConversation(aiAgent);
|
|
53389
|
+
}
|
|
53390
|
+
});
|
|
53391
|
+
}
|
|
53392
|
+
function findAgentById(agentId) {
|
|
53393
|
+
return agentsCache.get(agentId);
|
|
53394
|
+
}
|
|
53395
|
+
// Only conversation agents belong to this plugin; the rest are owned
|
|
53396
|
+
// by the built-in PromptAnalytics plugin.
|
|
53397
|
+
function isConversationAgent(agent) {
|
|
53398
|
+
return DOMConversation.supportedPreset(agent.preset);
|
|
53399
|
+
}
|
|
53400
|
+
function onConfigLoaded(aiAgents = []) {
|
|
53401
|
+
if (!aiAgents || !_.isArray(aiAgents) || aiAgents.length === 0) {
|
|
53402
|
+
return;
|
|
53403
|
+
}
|
|
53404
|
+
agentsCache.clear();
|
|
53405
|
+
_.each(aiAgents, aiAgent => {
|
|
53406
|
+
if (!isConversationAgent(aiAgent)) {
|
|
53407
|
+
return;
|
|
53408
|
+
}
|
|
53409
|
+
agentsCache.set(aiAgent.id, aiAgent);
|
|
53410
|
+
if (testPageRule(aiAgent.pageRule, aiAgent.id)) {
|
|
53411
|
+
observeConversation(aiAgent);
|
|
53412
|
+
}
|
|
53413
|
+
});
|
|
53414
|
+
}
|
|
53415
|
+
function observeConversation(config) {
|
|
53416
|
+
const { id, cssSelectors } = config;
|
|
53417
|
+
// Check if this agent is already being tracked
|
|
53418
|
+
const existing = _.find(conversations, conversation => conversation.id === id);
|
|
53419
|
+
if (existing) {
|
|
53420
|
+
return;
|
|
53421
|
+
}
|
|
53422
|
+
const missing = DOMConversation.validateConfig(cssSelectors);
|
|
53423
|
+
if (missing.length) {
|
|
53424
|
+
log.error('aiAgent config missing required selectors', { agentId: id, missing });
|
|
53425
|
+
return;
|
|
53426
|
+
}
|
|
53427
|
+
const conversation = new DOMConversation(pendo, api, id, cssSelectors);
|
|
53428
|
+
conversation.onSubmit(sendConversationEvent);
|
|
53429
|
+
conversations.push(conversation);
|
|
53430
|
+
}
|
|
53431
|
+
function sendConversationEvent(eventType, eventPayload) {
|
|
53432
|
+
// Block event if the conversation is suspended
|
|
53433
|
+
const conversation = _.find(conversations, c => c.id === eventPayload.agentId);
|
|
53434
|
+
if (conversation && !conversation.isActive) {
|
|
53435
|
+
return;
|
|
53436
|
+
}
|
|
53437
|
+
eventPayload = _.extend({ agentType: 'conversation' }, eventPayload);
|
|
53438
|
+
api.analytics.collectEvent(eventType, eventPayload, undefined, 'agentic');
|
|
53439
|
+
}
|
|
53440
|
+
function teardown() {
|
|
53441
|
+
_.each(conversations, conversation => conversation.teardown());
|
|
53442
|
+
conversations = [];
|
|
53443
|
+
agentsCache.clear();
|
|
53444
|
+
}
|
|
53445
|
+
}
|
|
53446
|
+
|
|
53447
|
+
export { ConsoleCapture, ConversationAnalytics, Feedback, GuideMarkdown, NetworkCapture, PredictGuides, createReplayPlugin as Replay, TextCapture, VocPortal, loadAgent };
|