@qualified-salesforce/web 0.0.1-oidc-bootstrap → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CAPABILITY-REFERENCE.md +41 -0
- package/CHANGELOG.md +84 -0
- package/README.md +65 -0
- package/constants.generated.js +30 -2
- package/css/widget/multimodal_v2/index.split-css.panda.css +265 -47
- package/package.json +18 -4
- package/packs/widget/api/docking/viewport_lock.js +93 -2
- package/packs/widget/esm/index.d.ts +134 -0
- package/packs/widget/esm/index.js +18 -2
- package/packs/widget/esm/qualified.css +265 -47
- package/src/graphql-types.js +74 -1
- package/src/url.js +4 -1
- package/vendor/actioncable/connection.js +40 -3
- package/vendor/actioncable/connection_monitor.js +8 -0
- package/vendor/actioncable/consumer.js +7 -1
- package/vendor/actioncable/index.js +5 -2
- package/vendor/actioncable/sse_socket.js +262 -0
- package/widget/analytics.js +8 -0
- package/widget/api_handler/base_api_handler.js +24 -0
- package/widget/booker_outcome_tracker.js +58 -20
- package/widget/capabilities.js +190 -0
- package/widget/channel.js +38 -6
- package/widget/constants.generated.js +2 -1
- package/widget/multimodal_v2/components/AppLayout.js +1 -1
- package/widget/multimodal_v2/components/app/Foreground.js +10 -9
- package/widget/multimodal_v2/components/app/Input.js +8 -3
- package/widget/multimodal_v2/components/app/LightboxExperience.js +17 -3
- package/widget/multimodal_v2/components/app/Main.js +87 -31
- package/widget/multimodal_v2/components/app/MessengerButton.js +18 -1
- package/widget/multimodal_v2/components/chat/ChatInputForm.js +8 -6
- package/widget/multimodal_v2/components/chat/timeline/CalendarEventPickRenderer.js +2 -2
- package/widget/multimodal_v2/components/chat/timeline/PresentDeckRenderer.js +346 -170
- package/widget/multimodal_v2/components/chat/timeline/SystemRenderer.js +4 -7
- package/widget/multimodal_v2/components/decks/index.js +506 -52
- package/widget/multimodal_v2/components/helpers/content_click_payloads.js +67 -1
- package/widget/multimodal_v2/components/lightbox_gather/index.js +4 -4
- package/widget/multimodal_v2/components/meetings/MeetingBookerEntry.js +8 -1
- package/widget/multimodal_v2/components/meetings/MeetingOfferEmailStep.js +1 -1
- package/widget/multimodal_v2/components/meetings/MeetingSummaryRail.js +1 -1
- package/widget/multimodal_v2/components/meetings/MeetingTimeSelector.js +1 -1
- package/widget/multimodal_v2/components/multimodal/Controls.js +11 -3
- package/widget/multimodal_v2/components/multimodal/useMultimodal.js +19 -7
- package/widget/multimodal_v2/components/presentations/index.js +61 -0
- package/widget/multimodal_v2/constants.js +1 -1
- package/widget/multimodal_v2/images/icons.js +17 -1
- package/widget/multimodal_v2/index.panda.js +2 -2
- package/widget/multimodal_v2/locales/cs.js +8 -1
- package/widget/multimodal_v2/locales/da.js +8 -1
- package/widget/multimodal_v2/locales/de.js +8 -1
- package/widget/multimodal_v2/locales/en.js +8 -1
- package/widget/multimodal_v2/locales/en_AU.js +8 -1
- package/widget/multimodal_v2/locales/en_GB.js +8 -1
- package/widget/multimodal_v2/locales/es.js +8 -1
- package/widget/multimodal_v2/locales/es_LA.js +8 -1
- package/widget/multimodal_v2/locales/fi.js +8 -1
- package/widget/multimodal_v2/locales/fr.js +8 -1
- package/widget/multimodal_v2/locales/fr_CA.js +8 -1
- package/widget/multimodal_v2/locales/id.js +8 -1
- package/widget/multimodal_v2/locales/it.js +8 -1
- package/widget/multimodal_v2/locales/ja.js +8 -1
- package/widget/multimodal_v2/locales/ko.js +8 -1
- package/widget/multimodal_v2/locales/nb.js +8 -1
- package/widget/multimodal_v2/locales/nl.js +8 -1
- package/widget/multimodal_v2/locales/pl.js +8 -1
- package/widget/multimodal_v2/locales/pt_BR.js +8 -1
- package/widget/multimodal_v2/locales/ru.js +8 -1
- package/widget/multimodal_v2/locales/sv.js +8 -1
- package/widget/multimodal_v2/locales/th.js +8 -1
- package/widget/multimodal_v2/locales/tr.js +8 -1
- package/widget/multimodal_v2/locales/vi.js +8 -1
- package/widget/multimodal_v2/locales/zh_CN.js +8 -1
- package/widget/multimodal_v2/locales/zh_TW.js +8 -1
- package/widget/multimodal_v2/multimodal.js +5 -1
- package/widget/multimodal_v2/multimodal_channel.js +13 -8
- package/widget/multimodal_v2/signals/messages_signal.js +4 -0
- package/widget/multimodal_v2/stores/ai_conversation_store.js +6 -2
- package/widget/multimodal_v2/stores/artifact_store.js +352 -27
- package/widget/multimodal_v2/stores/call_store.js +40 -1
- package/widget/multimodal_v2/stores/call_ui_adapter.js +1 -1
- package/widget/multimodal_v2/stores/decks_store.js +405 -39
- package/widget/multimodal_v2/stores/meeting_store.js +15 -10
- package/widget/multimodal_v2/stores/presentation_ga_adapter.js +64 -0
- package/widget/multimodal_v2/stores/presentation_telemetry_store.js +556 -0
- package/widget/multimodal_v2/stores/presentations_store.js +1 -0
- package/widget/multimodal_v2/stores/root_store.js +157 -268
- package/widget/multimodal_v2/stores/screen_share_store.js +41 -21
- package/widget/multimodal_v2/stores/translation_store.js +9 -0
- package/widget/multimodal_v2/stores/ui_store.js +7 -7
- package/widget/multimodal_v2/stores/website_navigation_store.js +3 -1
- package/widget/multimodal_v2/text_turn_response_tracker.js +419 -0
- package/widget/multimodal_v2/types.js +21 -1
- package/widget/multimodal_v2/utils/locale.js +2 -1
- package/widget/multimodal_v2/utils/signals.js +35 -4
- package/widget/pardot_visitor.js +7 -1
- package/widget/server-logger.js +97 -2
- package/widget/tracker.js +69 -21
- package/widget/widget/page_mirror_factory.js +9 -1
- package/widget/widget/widget.js +29 -4
|
@@ -57,6 +57,7 @@ class PresentationsStore {
|
|
|
57
57
|
presentation_id: pres.presentationId,
|
|
58
58
|
from_seconds: Math.round(this.currentTimeSeconds.value)
|
|
59
59
|
});
|
|
60
|
+
this.rootStore.presentationTelemetryStore.onDemoInterrupted();
|
|
60
61
|
this.stopped.value = true;
|
|
61
62
|
};
|
|
62
63
|
this.rootStore = rootStore;
|
|
@@ -5,30 +5,32 @@ import { BoundedSet } from "../../../src/bounded-set.js";
|
|
|
5
5
|
import { isDebugMode } from "../../../src/smart_website/utils.js";
|
|
6
6
|
import { snakify } from "../../../src/utils.js";
|
|
7
7
|
import { BookerIntentTracker } from "../../booker_intent_tracker.js";
|
|
8
|
-
import { BOOKER_PATHWAY_ORIGIN, BOOKER_SURFACE } from "../../booker_outcome_tracker.js";
|
|
8
|
+
import { reportBookerLoadFailed, BOOKER_PATHWAY_ORIGIN, bookerPathwayOrigin, BOOKER_SURFACE } from "../../booker_outcome_tracker.js";
|
|
9
|
+
import { isCapabilityAllowed, messageCapability } from "../../capabilities.js";
|
|
9
10
|
import { serverLogger } from "../../server-logger.js";
|
|
10
11
|
import "../../telemetry/index.js";
|
|
11
12
|
import { getSentryClient } from "../../widget/sentry.js";
|
|
12
13
|
import { CAN_OFFER_MEETING_BOOKER_EVENT_TYPE } from "../constants.js";
|
|
13
14
|
import { createActiveConversationSignal } from "../signals/active_conversation_signal.js";
|
|
14
15
|
import { createMessagesSignal, isStreamableMessage, isAssistantMessage, isEventMessage } from "../signals/messages_signal.js";
|
|
16
|
+
import { TextTurnResponseTracker } from "../text_turn_response_tracker.js";
|
|
15
17
|
import { $fromImport } from "../utils/signals.js";
|
|
16
18
|
import { ArtifactStore } from "./artifact_store.js";
|
|
17
19
|
import { getCallUiAdapter } from "./call_ui_adapter.js";
|
|
18
20
|
import { DockingStore } from "./docking_store.js";
|
|
21
|
+
import { PresentationTelemetryStore } from "./presentation_telemetry_store.js";
|
|
19
22
|
import { TranslationStore } from "./translation_store.js";
|
|
20
23
|
import { UIStore } from "./ui_store.js";
|
|
21
24
|
import { BaseStore } from "./base_store.js";
|
|
22
25
|
import "../../utils/native_globals.js";
|
|
26
|
+
import { ConversationMode as types_ConversationMode, LoadingState as types_LoadingState, MaximizedState as types_MaximizedState, DisplayMode as types_DisplayMode, InputState as types_InputState } from "../types.js";
|
|
23
27
|
import { validateEmail } from "../../../src/validation.js.js";
|
|
24
|
-
import { ConversationMode as types_ConversationMode, LoadingState as types_LoadingState, DisplayMode as types_DisplayMode, MaximizedState as types_MaximizedState, InputState as types_InputState } from "../types.js";
|
|
25
28
|
import { buildCustEvChatViewPayload, buildCustEvContentImpressionPayload } from "../components/helpers/content_click_payloads.js";
|
|
26
29
|
import { esm_browser_v4 } from "../../../js/4101.js";
|
|
30
|
+
import { PresentationGaAdapter } from "./presentation_ga_adapter.js";
|
|
27
31
|
import { metrics } from "../../telemetry/metrics.js";
|
|
28
32
|
|
|
29
33
|
/* provided dependency */ var __nativeDocumentRemoveEventListener = __webpack_require__(68034).LA;
|
|
30
|
-
/* provided dependency */ var root_store_setTimeout = __webpack_require__(68034).pS;
|
|
31
|
-
/* provided dependency */ var root_store_clearTimeout = __webpack_require__(68034).Kj;
|
|
32
34
|
/* provided dependency */ var __nativeDocumentAddEventListener = __webpack_require__(68034).Qj;
|
|
33
35
|
function _extends() {
|
|
34
36
|
_extends = Object.assign || function(target) {
|
|
@@ -79,6 +81,10 @@ function _object_without_properties_loose(source, excluded) {
|
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
82
88
|
|
|
83
89
|
|
|
84
90
|
|
|
@@ -212,132 +218,25 @@ class RootStore extends BaseStore {
|
|
|
212
218
|
(_this_callStore_value = this.callStore.value) == null ? void 0 : _this_callStore_value.dispose();
|
|
213
219
|
(_this_waitingStore_value = this.waitingStore.value) == null ? void 0 : _this_waitingStore_value.dispose();
|
|
214
220
|
(_this_websiteNavigationStore_value = this.websiteNavigationStore.value) == null ? void 0 : _this_websiteNavigationStore_value.dispose();
|
|
221
|
+
this.presentationGaAdapter.dispose();
|
|
222
|
+
this.presentationTelemetryStore.dispose();
|
|
215
223
|
(_this_presentationsStore_value = this.presentationsStore.value) == null ? void 0 : _this_presentationsStore_value.dispose();
|
|
216
224
|
(_this_decksStore_value = this.decksStore.value) == null ? void 0 : _this_decksStore_value.dispose();
|
|
217
225
|
(_this_screenShareStore_value = this.screenShareStore.value) == null ? void 0 : _this_screenShareStore_value.dispose();
|
|
218
|
-
this.
|
|
219
|
-
this.flushHeldTimeout();
|
|
220
|
-
this.turnToken++;
|
|
226
|
+
this.textTurnResponseTracker.dispose();
|
|
221
227
|
if (typeof document !== "undefined") {
|
|
222
228
|
__nativeDocumentRemoveEventListener("visibilitychange", this.handleTurnVisibilityChange);
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
|
-
startTextTurn(messageClientId) {
|
|
226
|
-
// Before any per-turn state resets: the held metric belongs to the turn that
|
|
227
|
-
// just timed out and reads `turnCause` when it fires.
|
|
228
|
-
this.flushHeldTimeout();
|
|
229
|
-
this.lastVisitorMessageSentAt = performance.now();
|
|
230
|
-
this.turnMessageClientId = messageClientId;
|
|
231
|
-
this.turnCounterEmitted = false;
|
|
232
|
-
this.turnReplyHandled = false;
|
|
233
|
-
this.turnObscured = this.isDocumentHidden();
|
|
234
|
-
this.turnCallActive = this.callInProgress.peek();
|
|
235
|
-
this.turnCause = null;
|
|
236
|
-
this.turnHasToolCall = false;
|
|
237
|
-
const token = ++this.turnToken;
|
|
238
|
-
this.clearResponseTimeout();
|
|
239
|
-
this.responseTimeoutId = root_store_setTimeout(()=>{
|
|
240
|
-
this.responseTimeoutId = null;
|
|
241
|
-
if (token !== this.turnToken || this.turnCounterEmitted) return;
|
|
242
|
-
// Treat the turn as obscured if the tab was hidden at any point, not just
|
|
243
|
-
// now: backgrounded tabs throttle setTimeout, so the timer often fires only
|
|
244
|
-
// after the visitor returns (document.hidden already false). Checking the
|
|
245
|
-
// whole wait keeps that throttling delay out of the timeout (unavailable) count.
|
|
246
|
-
const obscured = this.turnObscured || this.isDocumentHidden();
|
|
247
|
-
// The turn is counted from this moment, whenever the metric actually
|
|
248
|
-
// leaves: a reply or a `textResponseEnded` arriving later must not talk us
|
|
249
|
-
// out of a timeout the visitor has already experienced.
|
|
250
|
-
this.turnCounterEmitted = true;
|
|
251
|
-
if (obscured) {
|
|
252
|
-
metrics.emitIncrement("multimodal_text_response", {
|
|
253
|
-
status: "backgrounded",
|
|
254
|
-
call_active: this.turnCallActiveTag
|
|
255
|
-
});
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
// Before the breadcrumb: nothing between latching the outcome and arming the
|
|
259
|
-
// emission, so a counted timeout cannot be lost to a failure in between.
|
|
260
|
-
this.holdTimeoutForCause();
|
|
261
|
-
serverLogger.serverLog({
|
|
262
|
-
type: "log",
|
|
263
|
-
message: "Multimodal text reply did not arrive within response budget"
|
|
264
|
-
});
|
|
265
|
-
}, (/* inlined export .RESPONSE_BUDGET_MS */25000));
|
|
266
|
-
return token;
|
|
267
|
-
}
|
|
268
|
-
get turnCallActiveTag() {
|
|
269
|
-
return this.turnCallActive ? "true" : "false";
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Holds a decided timeout's metric back briefly so the server's cause report
|
|
273
|
-
* can land on it (see CAUSE_REPORT_GRACE_MS). Only the `cause` tag is
|
|
274
|
-
* outstanding — the turn is already counted — so this delays an emission, it
|
|
275
|
-
* never changes an outcome.
|
|
276
|
-
*
|
|
277
|
-
* Ends early when the report arrives, and is flushed rather than dropped if the
|
|
278
|
-
* turn is superseded or the store torn down first, so a held timeout can't go
|
|
279
|
-
* missing from the SLI.
|
|
280
|
-
*/ holdTimeoutForCause() {
|
|
281
|
-
// Flush rather than clear: a hold here would already have been counted, and
|
|
282
|
-
// dropping it would delete a failure from the SLI. Unreachable today (one
|
|
283
|
-
// budget timer per turn, and starting a turn flushes), enforced here so it
|
|
284
|
-
// stays that way.
|
|
285
|
-
this.flushHeldTimeout();
|
|
286
|
-
// Captured now: a turn starting during the window resets the live value.
|
|
287
|
-
const callActive = this.turnCallActiveTag;
|
|
288
|
-
this.heldTimeoutFlush = ()=>{
|
|
289
|
-
var _this_turnCause;
|
|
290
|
-
this.clearTimeoutHold();
|
|
291
|
-
metrics.emitIncrement("multimodal_text_response", {
|
|
292
|
-
status: "timeout",
|
|
293
|
-
call_active: callActive,
|
|
294
|
-
// `unknown` rather than omitted: every timeout carries a cause, so a
|
|
295
|
-
// dashboard split by cause always adds up to the timeout total. A growing
|
|
296
|
-
// `unknown` slice means the report is not reaching us.
|
|
297
|
-
cause: (_this_turnCause = this.turnCause) != null ? _this_turnCause : "unknown"
|
|
298
|
-
});
|
|
299
|
-
};
|
|
300
|
-
this.timeoutHoldId = root_store_setTimeout(this.heldTimeoutFlush, (/* inlined export .CAUSE_REPORT_GRACE_MS */1500));
|
|
301
|
-
}
|
|
302
|
-
// No-op unless a timeout is being held. Self-clearing, so it is safe to call
|
|
303
|
-
// from every path that might race the window.
|
|
304
|
-
flushHeldTimeout() {
|
|
305
|
-
this.heldTimeoutFlush == null ? void 0 : this.heldTimeoutFlush.call(this);
|
|
306
|
-
}
|
|
307
|
-
clearTimeoutHold() {
|
|
308
|
-
if (this.timeoutHoldId !== null) {
|
|
309
|
-
root_store_clearTimeout(this.timeoutHoldId);
|
|
310
|
-
this.timeoutHoldId = null;
|
|
311
|
-
}
|
|
312
|
-
this.heldTimeoutFlush = null;
|
|
313
|
-
}
|
|
314
|
-
clearResponseTimeout() {
|
|
315
|
-
if (this.responseTimeoutId !== null) {
|
|
316
|
-
root_store_clearTimeout(this.responseTimeoutId);
|
|
317
|
-
this.responseTimeoutId = null;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
231
|
isDocumentHidden() {
|
|
321
232
|
return typeof document !== "undefined" && document.hidden;
|
|
322
233
|
}
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
call_active: this.turnCallActiveTag
|
|
330
|
-
});
|
|
331
|
-
// A failed send has no reply coming, so close the turn entirely (no latency).
|
|
332
|
-
this.turnCounterEmitted = true;
|
|
333
|
-
this.turnReplyHandled = true;
|
|
334
|
-
this.turnToken++;
|
|
335
|
-
}
|
|
336
|
-
static isTextResponseEndedReason(reason) {
|
|
337
|
-
return RootStore.TEXT_RESPONSE_ENDED_REASONS.includes(reason);
|
|
338
|
-
}
|
|
339
|
-
static isTurnCause(cause) {
|
|
340
|
-
return RootStore.TURN_CAUSES.includes(cause);
|
|
234
|
+
recordArtifactVisibilityTransition(previousState, state, message, presentationEndReason) {
|
|
235
|
+
if (presentationEndReason === void 0) presentationEndReason = "dismissed";
|
|
236
|
+
if (previousState !== types_MaximizedState.ARTIFACT || state === types_MaximizedState.ARTIFACT) return;
|
|
237
|
+
if ((message == null ? void 0 : message.type) === "present_content" || (message == null ? void 0 : message.type) === "present_deck" || (message == null ? void 0 : message.type) === "present_video") {
|
|
238
|
+
this.presentationTelemetryStore.onArtifactHidden(message.id, presentationEndReason);
|
|
239
|
+
}
|
|
341
240
|
}
|
|
342
241
|
unresolvedAiSdrEmailRequest() {
|
|
343
242
|
var _this_activeConversation_value;
|
|
@@ -437,7 +336,7 @@ class RootStore extends BaseStore {
|
|
|
437
336
|
log: (params)=>serverLogger.serverLog(params)
|
|
438
337
|
}), // Translation store is directly instantiated (not lazy-loaded) because
|
|
439
338
|
// translations are needed immediately when the app renders
|
|
440
|
-
this.translationStore = new TranslationStore(this), this.dockingStore = new DockingStore(this), this.artifactStore = new ArtifactStore(this), // Mutable State
|
|
339
|
+
this.translationStore = new TranslationStore(this), this.dockingStore = new DockingStore(this), this.presentationTelemetryStore = new PresentationTelemetryStore(this), this.artifactStore = new ArtifactStore(this), // Mutable State
|
|
441
340
|
this.isVisitorBlocked = signals_core_module_d(false), /** Piper form join ID for this page view; same for all custEv_* events on the page. */ this.formJoinIDUnique = signals_core_module_d(null), /** Tracks fired custEv_contentImpression IDs; bounded to prevent unbounded growth. */ this.impressedMessageIds = new BoundedSet(RootStore.IMPRESSED_MESSAGE_IDS_MAX_SIZE), this.agentsAvailable = signals_core_module_d(false), this.companyName = signals_core_module_d(), this.disableTitleFlash = signals_core_module_d(false), this.messengerEventListener = null, this.callInProgress = signals_core_module_w(()=>{
|
|
442
341
|
var _this_callStore_value;
|
|
443
342
|
return ((_this_callStore_value = this.callStore.value) == null ? void 0 : _this_callStore_value.dailyCall.value) != null;
|
|
@@ -445,20 +344,9 @@ class RootStore extends BaseStore {
|
|
|
445
344
|
// via the multimodal voice greeting setting). When true, the initial greeting
|
|
446
345
|
// can be multiple sentences and varies per team, which affects greeting-sensitive
|
|
447
346
|
// timing metrics. Used to tag those metrics so dashboards can filter them out.
|
|
448
|
-
this.hasCustomVoiceGreeting = false, this.pounceTrackingLoadStarted = false, this.
|
|
449
|
-
|
|
450
|
-
this.
|
|
451
|
-
// server's cause report (see holdTimeoutForCause).
|
|
452
|
-
this.timeoutHoldId = null, this.heldTimeoutFlush = null, // Dedups a late reply against a turn the timer already resolved.
|
|
453
|
-
this.turnToken = 0, // clientId of the message that opened the current turn; used to ignore a
|
|
454
|
-
// stale `textResponseEnded` event from a turn we've already superseded.
|
|
455
|
-
this.turnMessageClientId = null, // Latest cause the server reported for the outstanding turn, if any. Read only
|
|
456
|
-
// when the turn times out.
|
|
457
|
-
this.turnCause = null, // Whether the agent reached for a tool on this turn, which costs the call plus
|
|
458
|
-
// another model round trip to use what it returned. Reported as it happens, so a
|
|
459
|
-
// turn that never finishes is still on the right side of the split.
|
|
460
|
-
this.turnHasToolCall = false, // Tab hidden mid-turn → bucket as `backgrounded` (not a false `timeout`) and skip its latency.
|
|
461
|
-
this.turnObscured = false, this.turnCallActive = false, this.conversations = signals_core_module_d([]), this.messages = createMessagesSignal(), this.pounceCandidates = signals_core_module_d(null), this.isVideoLoading = signals_core_module_d(false), this.isBlurredVideoActive = signals_core_module_d(false), /**
|
|
347
|
+
this.hasCustomVoiceGreeting = false, this.pounceTrackingLoadStarted = false, this.textTurnResponseTracker = new TextTurnResponseTracker({
|
|
348
|
+
isCurrentlyHidden: ()=>this.isDocumentHidden()
|
|
349
|
+
}), this.conversations = signals_core_module_d([]), this.messages = createMessagesSignal(), this.pounceCandidates = signals_core_module_d(null), this.isVideoLoading = signals_core_module_d(false), this.isBlurredVideoActive = signals_core_module_d(false), /**
|
|
462
350
|
* Which provider's track Coffee has committed to as the avatar video for this call.
|
|
463
351
|
* Null until Coffee reports one (an older Coffee never will), which callers treat as
|
|
464
352
|
* "fall back to the per-team flag".
|
|
@@ -634,9 +522,7 @@ class RootStore extends BaseStore {
|
|
|
634
522
|
data
|
|
635
523
|
});
|
|
636
524
|
}, this.handleTurnVisibilityChange = ()=>{
|
|
637
|
-
if (this.isDocumentHidden()
|
|
638
|
-
this.turnObscured = true;
|
|
639
|
-
}
|
|
525
|
+
if (this.isDocumentHidden()) this.textTurnResponseTracker.onHidden();
|
|
640
526
|
}, // Actions — delegated to UIStore
|
|
641
527
|
this.setUIState = (uiState)=>{
|
|
642
528
|
this.uiStore.setUIState(uiState);
|
|
@@ -652,11 +538,25 @@ class RootStore extends BaseStore {
|
|
|
652
538
|
this.uiStore.toggleVideoMode(mode);
|
|
653
539
|
}, this.setMaximizedState = (state)=>{
|
|
654
540
|
this.uiStore.setMaximizedState(state);
|
|
541
|
+
}, this.endPresentationAndSetMaximizedState = (state, reason)=>{
|
|
542
|
+
if (reason === void 0) reason = "dismissed";
|
|
543
|
+
const previousState = this.maximizedState.peek();
|
|
544
|
+
const artifactMessage = this.artifact.message.peek();
|
|
545
|
+
this.uiStore.setMaximizedState(state);
|
|
546
|
+
this.recordArtifactVisibilityTransition(previousState, this.maximizedState.peek(), artifactMessage, reason);
|
|
655
547
|
}, this.clearMaximizedState = (param)=>{
|
|
656
548
|
let { when } = param === void 0 ? {} : param;
|
|
657
549
|
this.uiStore.clearMaximizedState({
|
|
658
550
|
when
|
|
659
551
|
});
|
|
552
|
+
}, this.endPresentationAndClearMaximizedState = (param)=>{
|
|
553
|
+
let { when, reason } = param === void 0 ? {} : param;
|
|
554
|
+
const previousState = this.maximizedState.peek();
|
|
555
|
+
const artifactMessage = this.artifact.message.peek();
|
|
556
|
+
this.uiStore.clearMaximizedState({
|
|
557
|
+
when
|
|
558
|
+
});
|
|
559
|
+
this.recordArtifactVisibilityTransition(previousState, this.maximizedState.peek(), artifactMessage, reason);
|
|
660
560
|
}, this.sendTypingEvent = (content)=>{
|
|
661
561
|
this.channel.perform("update_visitor_typing", {
|
|
662
562
|
content
|
|
@@ -718,62 +618,16 @@ class RootStore extends BaseStore {
|
|
|
718
618
|
if (payload.type === "text") {
|
|
719
619
|
this.recordPrivacyDisclosureConsent(id);
|
|
720
620
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
// aggregate, so we accept it rather than track per-message turns.
|
|
727
|
-
const startedTurnToken = isVisitorAction && (this.lastVisitorMessageSentAt === null || this.turnCounterEmitted) ? this.startTextTurn(id) : null;
|
|
621
|
+
const textTurnHandle = isVisitorAction ? this.textTurnResponseTracker.begin({
|
|
622
|
+
clientId: id,
|
|
623
|
+
callActive: this.callInProgress.peek(),
|
|
624
|
+
initiallyHidden: this.isDocumentHidden()
|
|
625
|
+
}) : null;
|
|
728
626
|
const result = await this.channel.perform("send_message", snakify(message));
|
|
729
|
-
if (
|
|
730
|
-
this.
|
|
627
|
+
if (textTurnHandle !== null) {
|
|
628
|
+
this.textTurnResponseTracker.handleSendResult(textTurnHandle, result);
|
|
731
629
|
}
|
|
732
630
|
return result;
|
|
733
|
-
}, /**
|
|
734
|
-
* Backend closed a turn with no reply on purpose (guardrail block, rep
|
|
735
|
-
* takeover, blank output, ...). Close the turn under a distinct status so the
|
|
736
|
-
* budget timer doesn't fire a false `timeout` and dashboards can exclude it
|
|
737
|
-
* from the success rate. Guarded on `turnCounterEmitted` so a turn already
|
|
738
|
-
* resolved by a real reply (or the timer) is left untouched.
|
|
739
|
-
*/ this.handleTextResponseEnded = (payload)=>{
|
|
740
|
-
var _ref;
|
|
741
|
-
if (this.lastVisitorMessageSentAt === null || this.turnCounterEmitted) return;
|
|
742
|
-
// Ignore a late event from a turn we've already superseded: it must belong
|
|
743
|
-
// to the message that opened the current outstanding turn.
|
|
744
|
-
if ((payload == null ? void 0 : payload.clientId) !== this.turnMessageClientId) return;
|
|
745
|
-
const reason = (_ref = payload == null ? void 0 : payload.reason) != null ? _ref : "";
|
|
746
|
-
const status = RootStore.isTextResponseEndedReason(reason) ? reason : "suppressed";
|
|
747
|
-
this.clearResponseTimeout();
|
|
748
|
-
metrics.emitIncrement("multimodal_text_response", _extends({
|
|
749
|
-
status,
|
|
750
|
-
call_active: this.turnCallActiveTag
|
|
751
|
-
}, this.turnCause ? {
|
|
752
|
-
cause: this.turnCause
|
|
753
|
-
} : {}));
|
|
754
|
-
// No reply is coming, so close the turn entirely (no latency recorded).
|
|
755
|
-
this.turnCounterEmitted = true;
|
|
756
|
-
this.turnReplyHandled = true;
|
|
757
|
-
this.turnToken++;
|
|
758
|
-
}, /**
|
|
759
|
-
* The server telling us what this turn is spending its time on, sent when the
|
|
760
|
-
* response budget expires (and again afterwards for turns that finished slow).
|
|
761
|
-
* Purely an annotation: it never resolves or ends a turn, it only supplies the
|
|
762
|
-
* `cause` tag the timeout would otherwise lack.
|
|
763
|
-
*/ this.handleTextTurnDiagnostics = (payload)=>{
|
|
764
|
-
var _ref;
|
|
765
|
-
if ((payload == null ? void 0 : payload.clientId) !== this.turnMessageClientId) return;
|
|
766
|
-
if ((payload == null ? void 0 : payload.hasToolCall) === true) this.turnHasToolCall = true;
|
|
767
|
-
// A tool report carries no cause and arrives mid-turn, so it must not release a
|
|
768
|
-
// held timeout: that would emit the metric before the cause it is waiting for.
|
|
769
|
-
const cause = (_ref = payload == null ? void 0 : payload.cause) != null ? _ref : "";
|
|
770
|
-
if (!RootStore.isTurnCause(cause)) return;
|
|
771
|
-
// Preserve the first concrete explanation for this client turn; `unknown`
|
|
772
|
-
// is a placeholder and may be replaced when a real cause arrives.
|
|
773
|
-
this.turnCause = this.turnCause && this.turnCause !== "unknown" ? this.turnCause : cause;
|
|
774
|
-
// This report is what a held timeout was waiting for; no reason to sit out
|
|
775
|
-
// the rest of the window.
|
|
776
|
-
this.flushHeldTimeout();
|
|
777
631
|
}, this.sendVisitorText = async (text, extra)=>{
|
|
778
632
|
this.sendTextError.value = null;
|
|
779
633
|
if (this.featureFlags.piperXCookieConsent && this.needsCookieConsent.value) {
|
|
@@ -859,8 +713,6 @@ class RootStore extends BaseStore {
|
|
|
859
713
|
signals_core_module_r(()=>{
|
|
860
714
|
this.showCookieConsentModal.value = false;
|
|
861
715
|
this.pendingMessage.value = null;
|
|
862
|
-
// Clearing the pending call also discards any stashed trigger context, so a
|
|
863
|
-
// later regular call is never mistaken for a presentation start.
|
|
864
716
|
this.pendingCallStart.value = null;
|
|
865
717
|
this.setDisplayMode(types_DisplayMode.CLOSED);
|
|
866
718
|
});
|
|
@@ -919,7 +771,7 @@ class RootStore extends BaseStore {
|
|
|
919
771
|
}, /** Hands an incoming message to the overlay. See ArtifactStore#queueArtifactMessage. */ this.queueArtifactMessage = (message, options)=>{
|
|
920
772
|
if (options === void 0) options = {};
|
|
921
773
|
return this.artifactStore.queueArtifactMessage(message, options);
|
|
922
|
-
}, /** Re-opens an artifact the visitor asked for from the timeline. */ this.presentArtifactMessage = (message)=>this.artifactStore.presentArtifactMessage(message), this.revisitDeckSlide = (message, slideIndex, pageTitle)=>this.artifactStore.revisitDeckSlide(message, slideIndex, pageTitle), this.clearArtifact = ()=>{
|
|
774
|
+
}, /** Re-opens an artifact the visitor asked for from the timeline. */ this.presentArtifactMessage = (message)=>this.artifactStore.presentArtifactMessage(message), this.revisitDeckSlide = (message, slideIndex, pageTitle)=>this.artifactStore.revisitDeckSlide(message, slideIndex, pageTitle), /** Opens a deck for the visitor to step through at their own pace. */ this.presentSelfGuidedDeck = (message)=>this.artifactStore.presentSelfGuidedDeck(message), this.clearArtifact = ()=>{
|
|
923
775
|
this.artifactStore.clearArtifact();
|
|
924
776
|
}, this.clearInputMessage = (param)=>{
|
|
925
777
|
let { when } = param === void 0 ? {} : param;
|
|
@@ -946,7 +798,7 @@ class RootStore extends BaseStore {
|
|
|
946
798
|
}
|
|
947
799
|
}, this.startCall = async (startCallContext, displayState)=>{
|
|
948
800
|
if (displayState === void 0) displayState = "open";
|
|
949
|
-
var _this_pounceTrackingStore_value
|
|
801
|
+
var _this_pounceTrackingStore_value;
|
|
950
802
|
if (this.conversationMode.text.peek()) {
|
|
951
803
|
return;
|
|
952
804
|
}
|
|
@@ -966,6 +818,7 @@ class RootStore extends BaseStore {
|
|
|
966
818
|
this.showCookieConsentModal.value = true;
|
|
967
819
|
return;
|
|
968
820
|
}
|
|
821
|
+
this.presentationTelemetryStore.recordPendingTrigger(startCallContext != null ? startCallContext : null);
|
|
969
822
|
this.callInitiatedAt = performance.now();
|
|
970
823
|
this.messengerEventListener == null ? void 0 : this.messengerEventListener.call(this, "callRequested");
|
|
971
824
|
(_this_pounceTrackingStore_value = this.pounceTrackingStore.value) == null ? void 0 : _this_pounceTrackingStore_value.notifyCallRequested();
|
|
@@ -979,14 +832,41 @@ class RootStore extends BaseStore {
|
|
|
979
832
|
});
|
|
980
833
|
this.applyStartCallDisplayState(displayState);
|
|
981
834
|
});
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
835
|
+
// Both ways into a deck presentation come through here — the deck's timeline
|
|
836
|
+
// card and the self-guided viewer's switch to voice — so the deck is put into
|
|
837
|
+
// its starting state once, here, rather than at either call site.
|
|
838
|
+
//
|
|
839
|
+
// Only for `open`, and after the display state has been applied: the other two
|
|
840
|
+
// positions have no overlay to show it in. `minimized` runs the call as
|
|
841
|
+
// picture-in-picture and has just cleared the overlay to get there, and
|
|
842
|
+
// `maximized` gives the overlay's slot to the call UI itself.
|
|
843
|
+
//
|
|
844
|
+
// Awaited: its watch has to be installed before the lifecycle below moves, or
|
|
845
|
+
// a start that fails first is never seen to have begun. It cannot hold us up.
|
|
846
|
+
if (displayState === "open") {
|
|
847
|
+
await this.artifactStore.startDeckPresentation(startCallContext == null ? void 0 : startCallContext.trigger);
|
|
985
848
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
(
|
|
849
|
+
try {
|
|
850
|
+
var _this_callStore_value;
|
|
851
|
+
const callStore = this.callStore.value;
|
|
852
|
+
if (!callStore) {
|
|
853
|
+
await this.initializeVoiceAndVideo();
|
|
854
|
+
}
|
|
855
|
+
const dailyCall = (_this_callStore_value = this.callStore.value) == null ? void 0 : _this_callStore_value.dailyCall.value;
|
|
856
|
+
if (!dailyCall) {
|
|
857
|
+
var _this_callStore_value1;
|
|
858
|
+
(_this_callStore_value1 = this.callStore.value) == null ? void 0 : _this_callStore_value1.aiConversationStore.startConversation(false, startCallContext != null ? startCallContext : null);
|
|
859
|
+
}
|
|
860
|
+
} catch (error) {
|
|
861
|
+
// The one way a start can end without the watch seeing it end: the call
|
|
862
|
+
// chunk failing to load leaves `isVideoLoading` raised by
|
|
863
|
+
// #initializeVoiceAndVideo's first line and nothing behind it to lower it,
|
|
864
|
+
// so the watch has seen a start begin that never finishes and the deadline
|
|
865
|
+
// stays unarmed behind a `connecting` that stays true. Every other failure
|
|
866
|
+
// brings the flag back down and is torn down by the watch itself. Rethrown
|
|
867
|
+
// so a caller that does look at this still sees what happened.
|
|
868
|
+
this.artifactStore.endDeckPresentation();
|
|
869
|
+
throw error;
|
|
990
870
|
}
|
|
991
871
|
}, /**
|
|
992
872
|
* Position the widget for a call that is about to start.
|
|
@@ -997,7 +877,7 @@ class RootStore extends BaseStore {
|
|
|
997
877
|
* closed, so it has to be cleared as well.
|
|
998
878
|
*/ this.applyStartCallDisplayState = (displayState)=>{
|
|
999
879
|
if (displayState === "minimized") {
|
|
1000
|
-
this.
|
|
880
|
+
this.endPresentationAndClearMaximizedState();
|
|
1001
881
|
this.setDisplayMode(types_DisplayMode.CLOSED);
|
|
1002
882
|
return;
|
|
1003
883
|
}
|
|
@@ -1008,7 +888,7 @@ class RootStore extends BaseStore {
|
|
|
1008
888
|
// Maximizing centers the multimodal UI, which collides with the custom entry
|
|
1009
889
|
// points voice-only setups render, so it is video-only.
|
|
1010
890
|
if (displayState === "maximized" && this.conversationMode.video.peek()) {
|
|
1011
|
-
this.
|
|
891
|
+
this.endPresentationAndSetMaximizedState(types_MaximizedState.MULTIMODAL);
|
|
1012
892
|
}
|
|
1013
893
|
}, this.resetToStartingPosition = ()=>{
|
|
1014
894
|
this.uiStore.resetToStartingPosition();
|
|
@@ -1086,11 +966,10 @@ class RootStore extends BaseStore {
|
|
|
1086
966
|
* - SIDEBAR → CLOSED
|
|
1087
967
|
*/ this.cancelCallOrConnection = async (param)=>{
|
|
1088
968
|
let { closeWidget = true } = param === void 0 ? {} : param;
|
|
1089
|
-
// Reset maximized state when ending call
|
|
1090
|
-
this.clearMaximizedState();
|
|
1091
969
|
// Handle debug mode - reset the scenario store
|
|
1092
970
|
const debugStore = this.debugScenarioStore.value;
|
|
1093
971
|
if (this.isDebugMultimodal.value && debugStore) {
|
|
972
|
+
this.endPresentationAndClearMaximizedState();
|
|
1094
973
|
debugStore.reset();
|
|
1095
974
|
if (closeWidget) {
|
|
1096
975
|
this.closeWidget();
|
|
@@ -1101,15 +980,20 @@ class RootStore extends BaseStore {
|
|
|
1101
980
|
const callStore = this.callStore.value;
|
|
1102
981
|
const aiConversationStore = callStore == null ? void 0 : callStore.aiConversationStore;
|
|
1103
982
|
const dailyCall = callStore == null ? void 0 : callStore.dailyCall.value;
|
|
983
|
+
const isCallBoundary = Boolean(aiConversationStore == null ? void 0 : aiConversationStore.conversation.value) || Boolean(dailyCall) || this.isVideoLoading.peek();
|
|
984
|
+
const presentationEndReason = isCallBoundary ? "call_ended" : "dismissed";
|
|
1104
985
|
// Always reset the loading state first to prevent any pending async operations
|
|
1105
986
|
// from continuing (e.g., tavusStore.handleStartResponse checks this flag)
|
|
1106
987
|
this.isVideoLoading.value = false;
|
|
1107
988
|
this.isBlurredVideoActive.value = false;
|
|
1108
989
|
this.activeAvatarProvider.value = null;
|
|
1109
990
|
this.setTypingIndicator(false);
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
991
|
+
this.endPresentationAndClearMaximizedState({
|
|
992
|
+
reason: presentationEndReason
|
|
993
|
+
});
|
|
994
|
+
// The backend does not echo callEnded for visitor-initiated ends.
|
|
995
|
+
if (callStore && isCallBoundary) {
|
|
996
|
+
await callStore.endCall();
|
|
1113
997
|
}
|
|
1114
998
|
// Leave any active Daily call
|
|
1115
999
|
if (dailyCall) {
|
|
@@ -1156,8 +1040,8 @@ class RootStore extends BaseStore {
|
|
|
1156
1040
|
}),
|
|
1157
1041
|
visitorUnblocked: ()=>this.isVisitorBlocked.value = false,
|
|
1158
1042
|
botProcessEnded: ()=>this.handleBotProcessEnded(),
|
|
1159
|
-
textResponseEnded: (payload)=>this.
|
|
1160
|
-
textTurnDiagnostics: (payload)=>this.
|
|
1043
|
+
textResponseEnded: (payload)=>this.textTurnResponseTracker.handleResponseEnded(payload),
|
|
1044
|
+
textTurnDiagnostics: (payload)=>this.textTurnResponseTracker.handleDiagnostic(payload),
|
|
1161
1045
|
toolCallStarted: (payload)=>{
|
|
1162
1046
|
var _ref;
|
|
1163
1047
|
this.activeToolCall.value = (_ref = payload == null ? void 0 : payload.toolName) != null ? _ref : null;
|
|
@@ -1194,7 +1078,7 @@ class RootStore extends BaseStore {
|
|
|
1194
1078
|
this.elementConversationTextMessage.value = null;
|
|
1195
1079
|
this.closeLightbox();
|
|
1196
1080
|
});
|
|
1197
|
-
this.
|
|
1081
|
+
this.loadBookerForElementOffer(offerMsg);
|
|
1198
1082
|
} else {
|
|
1199
1083
|
signals_core_module_r(()=>{
|
|
1200
1084
|
this.openMessengerForElementConversation();
|
|
@@ -1315,7 +1199,41 @@ class RootStore extends BaseStore {
|
|
|
1315
1199
|
this.elementConversationTextMessage.value = null;
|
|
1316
1200
|
this.closeLightbox();
|
|
1317
1201
|
});
|
|
1318
|
-
this.
|
|
1202
|
+
this.loadBookerForElementOffer(offerMessage);
|
|
1203
|
+
}, /**
|
|
1204
|
+
* Loads the booker for an element (form/CTA) offer already on screen.
|
|
1205
|
+
*
|
|
1206
|
+
* Channel handlers cannot await, so the rejection needs catching here or it
|
|
1207
|
+
* reaches the host page. Worth reporting because this surface has no card to
|
|
1208
|
+
* fall back to: the lightbox gates its meeting phase on the store being
|
|
1209
|
+
* ready, so a failed import just leaves the visitor on the spinner.
|
|
1210
|
+
*/ this.loadBookerForElementOffer = (offer)=>{
|
|
1211
|
+
this.meetingStore.load().catch((error)=>{
|
|
1212
|
+
var _getSentryClient;
|
|
1213
|
+
(_getSentryClient = getSentryClient({
|
|
1214
|
+
project: "multimodal"
|
|
1215
|
+
})) == null ? void 0 : _getSentryClient.captureException(error);
|
|
1216
|
+
this.reportBookerLoadFailure(offer, "element");
|
|
1217
|
+
});
|
|
1218
|
+
}, /**
|
|
1219
|
+
* Records an offer whose booker chunk never arrived as a `failed` outcome.
|
|
1220
|
+
*
|
|
1221
|
+
* The watchdog that normally resolves this SLI lives in `meetingStore`, inside
|
|
1222
|
+
* the chunk that did not load, so the offer would go unrecorded.
|
|
1223
|
+
*
|
|
1224
|
+
* @param conversationType The caller's pathway, which each knows statically.
|
|
1225
|
+
* Not read from `elementConversationOffer` here: this runs in an async
|
|
1226
|
+
* catch, by which point that signal may be cleared or hold another offer,
|
|
1227
|
+
* either of which mislabels the offer being reported.
|
|
1228
|
+
*/ this.reportBookerLoadFailure = (offer, conversationType)=>{
|
|
1229
|
+
reportBookerLoadFailed({
|
|
1230
|
+
surface: BOOKER_SURFACE.MULTIMODAL,
|
|
1231
|
+
offerMessageId: offer.id,
|
|
1232
|
+
pathwayOrigin: bookerPathwayOrigin(conversationType, offer.source),
|
|
1233
|
+
conversationType,
|
|
1234
|
+
// No `observeBookerOutcome` hand-off: it acts on `shown` and ignores the rest.
|
|
1235
|
+
log: (params)=>serverLogger.serverLog(params)
|
|
1236
|
+
});
|
|
1319
1237
|
}, this.handleElementConversationTextMessage = (payload)=>{
|
|
1320
1238
|
var _ref, _ref1, _ref2;
|
|
1321
1239
|
const title = (_ref = payload == null ? void 0 : payload.title) != null ? _ref : "";
|
|
@@ -1475,6 +1393,10 @@ class RootStore extends BaseStore {
|
|
|
1475
1393
|
if (stateUpdate.hasCustomVoiceGreeting !== undefined) {
|
|
1476
1394
|
this.hasCustomVoiceGreeting = stateUpdate.hasCustomVoiceGreeting;
|
|
1477
1395
|
}
|
|
1396
|
+
// Conversation#end! broadcasts this singular field rather than `conversations`.
|
|
1397
|
+
if (stateUpdate.activeConversation === null) {
|
|
1398
|
+
this.presentationTelemetryStore.onConversationEnded();
|
|
1399
|
+
}
|
|
1478
1400
|
if (stateUpdate.conversations) {
|
|
1479
1401
|
stateUpdate.conversations.forEach((conversation)=>{
|
|
1480
1402
|
this.updateConversation(conversation);
|
|
@@ -1514,44 +1436,10 @@ class RootStore extends BaseStore {
|
|
|
1514
1436
|
metrics.emitTiming("multimodal_first_pounce_ms", deltaMs);
|
|
1515
1437
|
this.pounceResponseStartTimestamp = null;
|
|
1516
1438
|
}
|
|
1517
|
-
if (isAssistantMessage(incomingMessage) && !incomingMessage.isPounce
|
|
1518
|
-
this.
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const obscured = this.turnObscured || this.isDocumentHidden();
|
|
1522
|
-
// Within budget the wait is short enough to trust regardless of visibility;
|
|
1523
|
-
// over budget we only trust the timing if the tab stayed foreground,
|
|
1524
|
-
// since a hidden tab can throttle delivery.
|
|
1525
|
-
if (withinBudget || !obscured) {
|
|
1526
|
-
metrics.emitTiming("multimodal_text_response_ms", deltaMs, {
|
|
1527
|
-
call_active: this.turnCallActiveTag,
|
|
1528
|
-
has_tool_call: this.turnHasToolCall ? "true" : "false"
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
if (!this.turnCounterEmitted) {
|
|
1532
|
-
var _this_turnCause;
|
|
1533
|
-
const status = withinBudget ? "success" : obscured ? "backgrounded" : "timeout";
|
|
1534
|
-
metrics.emitIncrement("multimodal_text_response", _extends({
|
|
1535
|
-
status,
|
|
1536
|
-
call_active: this.turnCallActiveTag
|
|
1537
|
-
}, status === "timeout" ? {
|
|
1538
|
-
cause: (_this_turnCause = this.turnCause) != null ? _this_turnCause : "unknown"
|
|
1539
|
-
} : {}));
|
|
1540
|
-
this.turnCounterEmitted = true;
|
|
1541
|
-
} else {
|
|
1542
|
-
// The 25s timer already fired and counted this turn as a timeout.
|
|
1543
|
-
// Log the late reply so we can distinguish "AI responded slowly" from
|
|
1544
|
-
// "reply never arrived" when investigating timeout events.
|
|
1545
|
-
serverLogger.serverLog({
|
|
1546
|
-
type: "log",
|
|
1547
|
-
message: "Multimodal text reply arrived after response budget (late)",
|
|
1548
|
-
extra: {
|
|
1549
|
-
context: "elapsed_ms:" + Math.round(deltaMs)
|
|
1550
|
-
}
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
this.turnReplyHandled = true;
|
|
1554
|
-
this.turnToken++;
|
|
1439
|
+
if (isAssistantMessage(incomingMessage) && !incomingMessage.isPounce) {
|
|
1440
|
+
this.textTurnResponseTracker.handleAssistantReply({
|
|
1441
|
+
currentlyHidden: this.isDocumentHidden()
|
|
1442
|
+
});
|
|
1555
1443
|
}
|
|
1556
1444
|
this.addMessage(incomingMessage, clientMessageIndex);
|
|
1557
1445
|
if (isAssistantMessage(incomingMessage)) {
|
|
@@ -1581,6 +1469,11 @@ class RootStore extends BaseStore {
|
|
|
1581
1469
|
}
|
|
1582
1470
|
}, this.handleIncomingMessage = (incomingMessage, param)=>{
|
|
1583
1471
|
let { wasHiddenByPlaceholder = false } = param === void 0 ? {} : param;
|
|
1472
|
+
// Capability boundary (QAI-5862): drop a message routed to a capability the consumer denied at
|
|
1473
|
+
// setup(). Gating at this single dispatch also blocks the second path navigate_content takes
|
|
1474
|
+
// into the nav store (via the artifact queue). No-op for the hosted widget (allow-all).
|
|
1475
|
+
const capability = messageCapability(incomingMessage.type);
|
|
1476
|
+
if (capability && !isCapabilityAllowed(capability)) return;
|
|
1584
1477
|
switch(incomingMessage.type){
|
|
1585
1478
|
case "calendar_event_offer":
|
|
1586
1479
|
// Closes the pre-offer SLI here, at message *receipt*, which is where the
|
|
@@ -1868,6 +1761,19 @@ class RootStore extends BaseStore {
|
|
|
1868
1761
|
// Pounce tracking is non-critical - fail silently
|
|
1869
1762
|
});
|
|
1870
1763
|
};
|
|
1764
|
+
this.presentationGaAdapter = new PresentationGaAdapter({
|
|
1765
|
+
telemetry: this.presentationTelemetryStore,
|
|
1766
|
+
isEnabled: ()=>{
|
|
1767
|
+
var _this_config_featureFlags, _this_config;
|
|
1768
|
+
return !!((_this_config = this.config) == null ? void 0 : (_this_config_featureFlags = _this_config.featureFlags) == null ? void 0 : _this_config_featureFlags.piperWebTrackingEvents);
|
|
1769
|
+
},
|
|
1770
|
+
conversationId: ()=>this.piperConversationId,
|
|
1771
|
+
formJoinIDUnique: ()=>{
|
|
1772
|
+
var _this_formJoinIDUnique_value;
|
|
1773
|
+
return (_this_formJoinIDUnique_value = this.formJoinIDUnique.value) != null ? _this_formJoinIDUnique_value : "";
|
|
1774
|
+
},
|
|
1775
|
+
emit: (event)=>this.channel.emitPiperWebTrackingEvent(event)
|
|
1776
|
+
});
|
|
1871
1777
|
// WS message handlers
|
|
1872
1778
|
for (const [event, handler] of Object.entries(this.messageHandlers)){
|
|
1873
1779
|
this.channel.on(event, handler);
|
|
@@ -1889,23 +1795,6 @@ RootStore.IMPRESSED_MESSAGE_IDS_MAX_SIZE = 100;
|
|
|
1889
1795
|
* placeholder, so the visitor can read the transcript text that just filled it
|
|
1890
1796
|
* before the overlay covers the page.
|
|
1891
1797
|
*/ RootStore.MEETING_BOOKER_PLACEHOLDER_DELAY_MS = 1000;
|
|
1892
|
-
// Kept in sync with MultimodalAgentWorker#broadcast_text_response_ended.
|
|
1893
|
-
RootStore.TEXT_RESPONSE_ENDED_REASONS = [
|
|
1894
|
-
"suppressed",
|
|
1895
|
-
"blank_output"
|
|
1896
|
-
];
|
|
1897
|
-
// Kept in sync with AiConversations::TurnCause::CAUSES.
|
|
1898
|
-
RootStore.TURN_CAUSES = [
|
|
1899
|
-
"queue_wait",
|
|
1900
|
-
"setup",
|
|
1901
|
-
"llm_slow",
|
|
1902
|
-
"agent_loop",
|
|
1903
|
-
"tool_slow",
|
|
1904
|
-
"side_llm_wait",
|
|
1905
|
-
"generation_failed",
|
|
1906
|
-
"output_moderation_retry",
|
|
1907
|
-
"unknown"
|
|
1908
|
-
];
|
|
1909
1798
|
const RootStoreContext = Q(null);
|
|
1910
1799
|
/**
|
|
1911
1800
|
* Please avoid using this hook as much as possible!!!
|