@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __webpack_require__ } from "../../../js/runtime.js";
|
|
2
2
|
import { signals_core_module_E, signals_core_module_w } from "../../../js/multimodal_v2.js";
|
|
3
|
+
import { isCapabilityAllowed } from "../../capabilities.js";
|
|
3
4
|
import { getSentryClient } from "../../widget/sentry.js";
|
|
4
5
|
import "../../utils/native_globals.js";
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ import "../../utils/native_globals.js";
|
|
|
7
8
|
/* provided dependency */ var screen_share_store_clearInterval = __webpack_require__(68034).wm;
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
// Cap the longest side so the encoded JPEG stays small enough to travel through
|
|
11
13
|
// ActionCable -> Redis -> the OpenAI websocket comfortably, while remaining
|
|
12
14
|
// readable (text + faces) for the agent.
|
|
@@ -42,41 +44,51 @@ class ScreenShareStore {
|
|
|
42
44
|
// window and rejects page frames while it owns the slot, so hand the slot back on the way out.
|
|
43
45
|
this.clearPerception();
|
|
44
46
|
}
|
|
45
|
-
// Reactively push the current slide (on change) and clear perception when no slide is shown
|
|
46
|
-
// the call ends.
|
|
47
|
+
// Reactively push the current slide (on change) and clear perception when no slide is shown,
|
|
48
|
+
// while a replacement is loading, or when the call ends.
|
|
47
49
|
sync() {
|
|
48
50
|
// Server-side perception is disabled without the flag, so don't capture or ship frames.
|
|
49
51
|
if (!this.rootStore.featureFlags.piperPerceptionSlides) return;
|
|
52
|
+
// QAI-5864: don't capture or egress slide frames to the perception loop unless the consumer
|
|
53
|
+
// allowed the "slidePerception" capability. Defense-in-depth alongside the flag check — the
|
|
54
|
+
// runtime-config filter strips `piperPerceptionSlides` when denied, and this guard holds even
|
|
55
|
+
// if the flag reached us another way. Allow-all for the hosted widget (no boundary installed).
|
|
56
|
+
if (!isCapabilityAllowed("slidePerception")) return;
|
|
50
57
|
const slideUrl = this.currentSlideImageUrl.value;
|
|
58
|
+
const frameKey = this.currentSlideFrameKey.value;
|
|
51
59
|
const inCall = this.rootStore.callInProgress.value;
|
|
52
60
|
if (!inCall) {
|
|
53
61
|
this.clearPerception();
|
|
54
62
|
return;
|
|
55
63
|
}
|
|
56
|
-
if (slideUrl) {
|
|
57
|
-
if (
|
|
58
|
-
|
|
64
|
+
if (slideUrl && frameKey) {
|
|
65
|
+
if (frameKey !== this.lastPushedKey && frameKey !== this.pendingKey) {
|
|
66
|
+
// The previous frame no longer matches what's visible. Retract it before awaiting the
|
|
67
|
+
// replacement image so pointing cannot resolve against the old slide during the load.
|
|
68
|
+
// clearPerception() is a no-op for the first slide and after the first transition clear.
|
|
69
|
+
this.clearPerception();
|
|
70
|
+
void this.pushSlide(slideUrl, frameKey);
|
|
59
71
|
}
|
|
60
72
|
return;
|
|
61
73
|
}
|
|
62
74
|
// No slide on screen: clear whatever slide we last pushed.
|
|
63
75
|
this.clearPerception();
|
|
64
76
|
}
|
|
65
|
-
async pushSlide(url) {
|
|
66
|
-
this.pendingKey =
|
|
77
|
+
async pushSlide(url, frameKey) {
|
|
78
|
+
this.pendingKey = frameKey;
|
|
67
79
|
try {
|
|
68
80
|
const image = await this.loadImage(url);
|
|
69
|
-
if (this.
|
|
81
|
+
if (this.currentSlideFrameKey.peek() !== frameKey) return; // superseded while loading
|
|
70
82
|
// The call may have ended while the image was loading; don't push after call end
|
|
71
83
|
// (the !inCall branch of sync() has already cleared perception).
|
|
72
84
|
if (!this.rootStore.callInProgress.peek()) return;
|
|
73
85
|
const imageData = this.encodeImage(image);
|
|
74
86
|
if (!imageData) {
|
|
75
|
-
this.retractOnFailure(
|
|
87
|
+
this.retractOnFailure(frameKey);
|
|
76
88
|
return;
|
|
77
89
|
}
|
|
78
90
|
this.push(imageData);
|
|
79
|
-
this.lastPushedKey =
|
|
91
|
+
this.lastPushedKey = frameKey;
|
|
80
92
|
this.lastPushedImageData = imageData;
|
|
81
93
|
this.startKeepalive();
|
|
82
94
|
} catch (error) {
|
|
@@ -84,17 +96,15 @@ class ScreenShareStore {
|
|
|
84
96
|
(_getSentryClient = getSentryClient({
|
|
85
97
|
project: "multimodal"
|
|
86
98
|
})) == null ? void 0 : _getSentryClient.captureException(error);
|
|
87
|
-
this.retractOnFailure(
|
|
99
|
+
this.retractOnFailure(frameKey);
|
|
88
100
|
} finally{
|
|
89
|
-
if (this.pendingKey ===
|
|
101
|
+
if (this.pendingKey === frameKey) this.pendingKey = null;
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
retractOnFailure(url) {
|
|
97
|
-
if (this.currentSlideImageUrl.peek() !== url) return; // already superseded; leave it alone
|
|
104
|
+
// Ensure a slide that failed to load or encode cannot leave any stale frame behind. Replacement
|
|
105
|
+
// slides are already retracted before loading, so clearPerception() keeps this idempotent.
|
|
106
|
+
retractOnFailure(frameKey) {
|
|
107
|
+
if (this.currentSlideFrameKey.peek() !== frameKey) return; // already superseded; leave it alone
|
|
98
108
|
this.clearPerception();
|
|
99
109
|
}
|
|
100
110
|
loadImage(url) {
|
|
@@ -183,11 +193,21 @@ class ScreenShareStore {
|
|
|
183
193
|
if (slide) return (_slide_url = slide.url) != null ? _slide_url : null;
|
|
184
194
|
return null;
|
|
185
195
|
});
|
|
196
|
+
// A new presentation message is a fresh share even when it displays the same URL. The backend
|
|
197
|
+
// clears perception before broadcasting that message, so include its id to ensure the widget
|
|
198
|
+
// replaces the cleared frame instead of treating the share as an already-pushed slide.
|
|
199
|
+
this.currentSlideFrameKey = signals_core_module_w(()=>{
|
|
200
|
+
const url = this.currentSlideImageUrl.value;
|
|
201
|
+
const messageId = this.rootStore.artifact.messageId.value;
|
|
202
|
+
return url && messageId ? messageId + ":" + url : null;
|
|
203
|
+
});
|
|
186
204
|
this.disposeEffect = null;
|
|
187
|
-
// The slide
|
|
205
|
+
// The presentation-message/slide pair we last pushed, so we only send on change and know when
|
|
206
|
+
// to clear. Message identity matters because re-sharing the visible slide clears it server-side.
|
|
188
207
|
this.lastPushedKey = null;
|
|
189
|
-
// The slide
|
|
190
|
-
// still awaiting the image load; without this an in-flight
|
|
208
|
+
// The presentation-message/slide pair currently loading/encoding for a push. sync() can re-run
|
|
209
|
+
// while pushSlide is still awaiting the image load; without this an in-flight frame would be
|
|
210
|
+
// pushed twice.
|
|
191
211
|
this.pendingKey = null;
|
|
192
212
|
// The encoded payload we last pushed, so the keepalive can re-send it without re-encoding.
|
|
193
213
|
this.lastPushedImageData = null;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { signals_core_module_d, signals_core_module_w } from "../../../js/multimodal_v2.js";
|
|
2
2
|
import { importLocale, isSupported } from "../locales/index.js";
|
|
3
|
+
import { toIntlLocale } from "../utils/locale.js";
|
|
3
4
|
import { useRootStore } from "./root_store.js";
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
const DEFAULT_LOCALE = "en";
|
|
9
11
|
/**
|
|
10
12
|
* Flatten a nested translations object into a Map with dot-separated keys.
|
|
@@ -156,6 +158,13 @@ const DEFAULT_LOCALE = "en";
|
|
|
156
158
|
return DEFAULT_LOCALE;
|
|
157
159
|
});
|
|
158
160
|
/**
|
|
161
|
+
* Browser/Intl-compatible form of `effectiveLocale`. This is the shared
|
|
162
|
+
* normalization boundary: code that hands a locale to `Intl.*` or a
|
|
163
|
+
* `toLocale*String` call should read this instead of normalizing `effectiveLocale`
|
|
164
|
+
* itself at the call site (see INC-136 / QAI-5726 -- an unnormalized locale reaching
|
|
165
|
+
* Intl throws and crashes the widget render).
|
|
166
|
+
*/ this.effectiveIntlLocale = signals_core_module_w(()=>toIntlLocale(this.effectiveLocale.value));
|
|
167
|
+
/**
|
|
159
168
|
* Translate a key with optional field substitution.
|
|
160
169
|
*
|
|
161
170
|
* @param key - Dot-separated key path (e.g., "chat.enterMessage")
|
|
@@ -274,9 +274,9 @@ class UIStore extends BaseStore {
|
|
|
274
274
|
break;
|
|
275
275
|
}
|
|
276
276
|
}, /**
|
|
277
|
-
* Dismisses the widget to CLOSED state and
|
|
278
|
-
*
|
|
279
|
-
*
|
|
277
|
+
* Dismisses the widget to CLOSED state and records the manual dismissal for
|
|
278
|
+
* persistence and redirect recovery. Reopening starts a new open → compact →
|
|
279
|
+
* launcher cycle for experiences with a compact starting position.
|
|
280
280
|
*/ this.dismissWidget = ()=>{
|
|
281
281
|
signals_core_module_r(()=>{
|
|
282
282
|
this.rootStore.clearElementConversationState();
|
|
@@ -289,15 +289,15 @@ class UIStore extends BaseStore {
|
|
|
289
289
|
this.saveUIState.flush();
|
|
290
290
|
this.persistRedirectDismissedUIState();
|
|
291
291
|
}, /**
|
|
292
|
-
* Closes the widget
|
|
293
|
-
*
|
|
294
|
-
*
|
|
292
|
+
* Closes the opened widget to its configured starting position. Experiences
|
|
293
|
+
* configured to start opened have no compact position, so they close directly
|
|
294
|
+
* to the launcher.
|
|
295
295
|
*/ this.closeWidget = ()=>{
|
|
296
296
|
signals_core_module_r(()=>{
|
|
297
297
|
this.rootStore.clearElementConversationState();
|
|
298
298
|
this.rootStore.closeLightbox();
|
|
299
299
|
this.rootStore.clearArtifact();
|
|
300
|
-
if (this.
|
|
300
|
+
if (this.initialDisplayMode.value === types_InitialDisplayMode.OPENED) {
|
|
301
301
|
this.setDisplayMode(types_DisplayMode.CLOSED);
|
|
302
302
|
} else {
|
|
303
303
|
this.resetToStartingPosition();
|
|
@@ -85,7 +85,9 @@ class WebsiteNavigationStore {
|
|
|
85
85
|
}
|
|
86
86
|
// Minimize video if maximized so user can see the navigated content,
|
|
87
87
|
// this will also clear the slide if it is maximized with an artifact
|
|
88
|
-
this.rootStore.
|
|
88
|
+
this.rootStore.endPresentationAndClearMaximizedState({
|
|
89
|
+
reason: "replaced"
|
|
90
|
+
});
|
|
89
91
|
const promises = [];
|
|
90
92
|
// Perform soft navigation if URL is provided
|
|
91
93
|
if (message.url) {
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { __webpack_require__ } from "../../js/runtime.js";
|
|
2
|
+
import { serverLogger } from "../server-logger.js";
|
|
3
|
+
import "../telemetry/index.js";
|
|
4
|
+
import "./constants.js";
|
|
5
|
+
import "../utils/native_globals.js";
|
|
6
|
+
import { metrics } from "../telemetry/metrics.js";
|
|
7
|
+
|
|
8
|
+
/* provided dependency */ var text_turn_response_tracker_setTimeout = __webpack_require__(68034).pS;
|
|
9
|
+
/* provided dependency */ var text_turn_response_tracker_clearTimeout = __webpack_require__(68034).Kj;
|
|
10
|
+
function _extends() {
|
|
11
|
+
_extends = Object.assign || function(target) {
|
|
12
|
+
for(var i = 1; i < arguments.length; i++){
|
|
13
|
+
var source = arguments[i];
|
|
14
|
+
for(var key in source){
|
|
15
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
16
|
+
target[key] = source[key];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
return _extends.apply(this, arguments);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const defaultDependencies = {
|
|
28
|
+
now: ()=>performance.now(),
|
|
29
|
+
setTimer: (callback, delayMs)=>text_turn_response_tracker_setTimeout(callback, delayMs),
|
|
30
|
+
clearTimer: (timerId)=>text_turn_response_tracker_clearTimeout(timerId),
|
|
31
|
+
isCurrentlyHidden: ()=>typeof document !== "undefined" && document.hidden,
|
|
32
|
+
emitIncrement: (metricName, tags)=>metrics.emitIncrement(metricName, tags),
|
|
33
|
+
emitTiming: (metricName, deltaMs, tags)=>metrics.emitTiming(metricName, deltaMs, tags),
|
|
34
|
+
serverLog: (params)=>serverLogger.serverLog(params)
|
|
35
|
+
};
|
|
36
|
+
const TEXT_RESPONSE_ENDED_REASONS = [
|
|
37
|
+
"suppressed",
|
|
38
|
+
"blank_output"
|
|
39
|
+
];
|
|
40
|
+
const isTextResponseEndedReason = (reason)=>TEXT_RESPONSE_ENDED_REASONS.includes(reason);
|
|
41
|
+
// Kept in sync with AiConversations::TurnCause::CAUSES.
|
|
42
|
+
const TURN_CAUSES = [
|
|
43
|
+
"queue_wait",
|
|
44
|
+
"setup",
|
|
45
|
+
"llm_slow",
|
|
46
|
+
"agent_loop",
|
|
47
|
+
"tool_slow",
|
|
48
|
+
"side_llm_wait",
|
|
49
|
+
"generation_failed",
|
|
50
|
+
"output_moderation_retry",
|
|
51
|
+
"unknown"
|
|
52
|
+
];
|
|
53
|
+
const isRecognizedTurnCause = (cause)=>TURN_CAUSES.includes(cause);
|
|
54
|
+
const CAUSE_REPORT_SOURCES = [
|
|
55
|
+
"deadline_watchdog",
|
|
56
|
+
"slow_turn_completion",
|
|
57
|
+
"output_moderation_retry"
|
|
58
|
+
];
|
|
59
|
+
const causeReportSourceForLog = (source)=>CAUSE_REPORT_SOURCES.includes(source != null ? source : "") ? source : "unavailable";
|
|
60
|
+
/**
|
|
61
|
+
* Owns the non-reactive lifecycle and telemetry for one outstanding text turn.
|
|
62
|
+
* RootStore remains responsible for deciding which messages start and resolve a turn.
|
|
63
|
+
*/ class TextTurnResponseTracker {
|
|
64
|
+
begin(param) {
|
|
65
|
+
let { clientId, callActive, initiallyHidden } = param;
|
|
66
|
+
// First-message-wins while a turn is still outstanding.
|
|
67
|
+
if (this.sentAt !== null && !this.counterEmitted) return null;
|
|
68
|
+
// A held metric belongs to the prior turn and reads its cause when flushed.
|
|
69
|
+
this.flushCauseHoldFor("turn_superseded");
|
|
70
|
+
this.flushHeldTimeout();
|
|
71
|
+
if (this.timedOut && !this.acceptedOutcomeLogged && this.clientId && this.sentAt !== null) {
|
|
72
|
+
var _this_timeoutFlushReason;
|
|
73
|
+
this.previousTimedOutTurn = {
|
|
74
|
+
clientId: this.clientId,
|
|
75
|
+
deadlineAt: this.sentAt + (/* inlined export .RESPONSE_BUDGET_MS */25000),
|
|
76
|
+
flushReason: (_this_timeoutFlushReason = this.timeoutFlushReason) != null ? _this_timeoutFlushReason : "turn_superseded"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
this.sentAt = this.dependencies.now();
|
|
80
|
+
this.clientId = clientId;
|
|
81
|
+
this.counterEmitted = false;
|
|
82
|
+
this.replyHandled = false;
|
|
83
|
+
this.obscured = initiallyHidden;
|
|
84
|
+
this.callActive = callActive;
|
|
85
|
+
this.cause = null;
|
|
86
|
+
this.hasToolCall = false;
|
|
87
|
+
this.timedOut = false;
|
|
88
|
+
this.causeHoldActive = false;
|
|
89
|
+
this.timeoutFlushReason = null;
|
|
90
|
+
this.acceptedCauseReceivedAt = null;
|
|
91
|
+
this.acceptedCauseReportSource = "unavailable";
|
|
92
|
+
this.acceptedOutcomeLogged = false;
|
|
93
|
+
this.unrecognizedCause = null;
|
|
94
|
+
this.unrecognizedCauseReportSource = "unavailable";
|
|
95
|
+
this.unrecognizedCauseReceivedAt = null;
|
|
96
|
+
const handle = ++this.turnToken;
|
|
97
|
+
this.clearResponseTimer();
|
|
98
|
+
this.responseTimerId = this.dependencies.setTimer(()=>{
|
|
99
|
+
var _this_clientId;
|
|
100
|
+
this.responseTimerId = null;
|
|
101
|
+
if (handle !== this.turnToken || this.counterEmitted) return;
|
|
102
|
+
const obscured = this.obscured || this.dependencies.isCurrentlyHidden();
|
|
103
|
+
this.counterEmitted = true;
|
|
104
|
+
if (obscured) {
|
|
105
|
+
this.dependencies.emitIncrement("multimodal_text_response", {
|
|
106
|
+
status: "backgrounded",
|
|
107
|
+
call_active: this.callActiveTag
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.clientTimedOut();
|
|
112
|
+
this.holdTimeoutForCause();
|
|
113
|
+
this.dependencies.serverLog({
|
|
114
|
+
type: "log",
|
|
115
|
+
message: "Multimodal text reply did not arrive within response budget",
|
|
116
|
+
extra: {
|
|
117
|
+
text_turn_client_id: (_this_clientId = this.clientId) != null ? _this_clientId : ""
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}, (/* inlined export .RESPONSE_BUDGET_MS */25000));
|
|
121
|
+
return handle;
|
|
122
|
+
}
|
|
123
|
+
handleSendResult(handle, result) {
|
|
124
|
+
if (!(result == null ? void 0 : result.error)) return;
|
|
125
|
+
if (handle !== this.turnToken || this.counterEmitted) return;
|
|
126
|
+
this.clearResponseTimer();
|
|
127
|
+
this.dependencies.emitIncrement("multimodal_text_response", {
|
|
128
|
+
status: "send_failed",
|
|
129
|
+
call_active: this.callActiveTag
|
|
130
|
+
});
|
|
131
|
+
this.counterEmitted = true;
|
|
132
|
+
this.replyHandled = true;
|
|
133
|
+
this.turnToken++;
|
|
134
|
+
}
|
|
135
|
+
handleAssistantReply(param) {
|
|
136
|
+
let { currentlyHidden } = param;
|
|
137
|
+
if (this.sentAt === null || this.replyHandled) return;
|
|
138
|
+
this.clearResponseTimer();
|
|
139
|
+
const deltaMs = this.dependencies.now() - this.sentAt;
|
|
140
|
+
const withinBudget = deltaMs <= (/* inlined export .RESPONSE_BUDGET_MS */25000);
|
|
141
|
+
const obscured = this.obscured || currentlyHidden;
|
|
142
|
+
if (withinBudget || !obscured) {
|
|
143
|
+
this.dependencies.emitTiming("multimodal_text_response_ms", deltaMs, {
|
|
144
|
+
call_active: this.callActiveTag,
|
|
145
|
+
has_tool_call: this.hasToolCall ? "true" : "false"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (!this.counterEmitted) {
|
|
149
|
+
var _this_cause;
|
|
150
|
+
const status = withinBudget ? "success" : obscured ? "backgrounded" : "timeout";
|
|
151
|
+
this.dependencies.emitIncrement("multimodal_text_response", _extends({
|
|
152
|
+
status,
|
|
153
|
+
call_active: this.callActiveTag
|
|
154
|
+
}, status === "timeout" ? {
|
|
155
|
+
cause: (_this_cause = this.cause) != null ? _this_cause : "unknown"
|
|
156
|
+
} : {}));
|
|
157
|
+
this.counterEmitted = true;
|
|
158
|
+
} else {
|
|
159
|
+
this.dependencies.serverLog({
|
|
160
|
+
type: "log",
|
|
161
|
+
message: "Multimodal text reply arrived after response budget (late)",
|
|
162
|
+
extra: {
|
|
163
|
+
context: "elapsed_ms:" + Math.round(deltaMs)
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
this.replyHandled = true;
|
|
168
|
+
this.turnToken++;
|
|
169
|
+
}
|
|
170
|
+
handleResponseEnded(payload) {
|
|
171
|
+
var _ref;
|
|
172
|
+
if (this.sentAt === null || this.counterEmitted) return;
|
|
173
|
+
if ((payload == null ? void 0 : payload.clientId) !== this.clientId) return;
|
|
174
|
+
const reason = (_ref = payload == null ? void 0 : payload.reason) != null ? _ref : "";
|
|
175
|
+
const status = isTextResponseEndedReason(reason) ? reason : "suppressed";
|
|
176
|
+
this.clearResponseTimer();
|
|
177
|
+
this.dependencies.emitIncrement("multimodal_text_response", _extends({
|
|
178
|
+
status,
|
|
179
|
+
call_active: this.callActiveTag
|
|
180
|
+
}, this.cause ? {
|
|
181
|
+
cause: this.cause
|
|
182
|
+
} : {}));
|
|
183
|
+
this.counterEmitted = true;
|
|
184
|
+
this.replyHandled = true;
|
|
185
|
+
this.turnToken++;
|
|
186
|
+
}
|
|
187
|
+
handleDiagnostic(payload) {
|
|
188
|
+
var _ref;
|
|
189
|
+
const cause = (_ref = payload == null ? void 0 : payload.cause) != null ? _ref : "";
|
|
190
|
+
const receivedAt = this.dependencies.now();
|
|
191
|
+
const causeReportSource = causeReportSourceForLog(payload == null ? void 0 : payload.causeReportSource);
|
|
192
|
+
if (this.handlePreviousTurnDiagnostic(payload, cause, causeReportSource, receivedAt)) return;
|
|
193
|
+
if (this.handleMismatchedDiagnostic(payload, cause, causeReportSource, receivedAt)) return;
|
|
194
|
+
if ((payload == null ? void 0 : payload.hasToolCall) === true) this.hasToolCall = true;
|
|
195
|
+
if (!cause) return;
|
|
196
|
+
if (!isRecognizedTurnCause(cause)) {
|
|
197
|
+
this.handleUnrecognizedCause(cause, causeReportSource, receivedAt);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.acceptDiagnosticCause(cause, causeReportSource, receivedAt);
|
|
201
|
+
}
|
|
202
|
+
onHidden() {
|
|
203
|
+
if (this.sentAt !== null && !this.replyHandled) this.obscured = true;
|
|
204
|
+
}
|
|
205
|
+
dispose() {
|
|
206
|
+
this.clearResponseTimer();
|
|
207
|
+
this.flushCauseHoldFor("store_disposed");
|
|
208
|
+
this.flushHeldTimeout();
|
|
209
|
+
this.turnToken++;
|
|
210
|
+
}
|
|
211
|
+
get callActiveTag() {
|
|
212
|
+
return this.callActive ? "true" : "false";
|
|
213
|
+
}
|
|
214
|
+
holdTimeoutForCause() {
|
|
215
|
+
this.flushHeldTimeout();
|
|
216
|
+
const callActive = this.callActiveTag;
|
|
217
|
+
this.heldTimeoutFlush = ()=>{
|
|
218
|
+
var _this_cause;
|
|
219
|
+
this.clearCauseHold();
|
|
220
|
+
this.dependencies.emitIncrement("multimodal_text_response", {
|
|
221
|
+
status: "timeout",
|
|
222
|
+
call_active: callActive,
|
|
223
|
+
cause: (_this_cause = this.cause) != null ? _this_cause : "unknown"
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
this.causeHoldTimerId = this.dependencies.setTimer(()=>{
|
|
227
|
+
this.flushCauseHoldFor("grace_expired");
|
|
228
|
+
this.heldTimeoutFlush == null ? void 0 : this.heldTimeoutFlush.call(this);
|
|
229
|
+
}, (/* inlined export .CAUSE_REPORT_GRACE_MS */1500));
|
|
230
|
+
}
|
|
231
|
+
handlePreviousTurnDiagnostic(payload, cause, causeReportSource, receivedAt) {
|
|
232
|
+
const previous = this.previousTimedOutTurn;
|
|
233
|
+
if (!previous || (payload == null ? void 0 : payload.clientId) !== previous.clientId || !isRecognizedTurnCause(cause)) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
this.logDiagnostic("accepted_after_timeout_flush", previous, {
|
|
237
|
+
cause,
|
|
238
|
+
causeReportSource,
|
|
239
|
+
receivedAt,
|
|
240
|
+
timeoutFlushReason: previous.flushReason
|
|
241
|
+
});
|
|
242
|
+
this.previousTimedOutTurn = null;
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
handleMismatchedDiagnostic(payload, cause, causeReportSource, receivedAt) {
|
|
246
|
+
if ((payload == null ? void 0 : payload.clientId) === this.clientId) return false;
|
|
247
|
+
// Cause-less tool progress is valid and deliberately excluded from mismatch noise.
|
|
248
|
+
if (this.timedOut && cause) {
|
|
249
|
+
const turn = this.currentDiagnosticTurn;
|
|
250
|
+
if (turn) {
|
|
251
|
+
this.logDiagnostic("client_id_mismatch", turn, {
|
|
252
|
+
cause,
|
|
253
|
+
causeReportSource,
|
|
254
|
+
receivedAt,
|
|
255
|
+
receivedTextTurnClientId: payload == null ? void 0 : payload.clientId
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
handleUnrecognizedCause(cause, causeReportSource, receivedAt) {
|
|
262
|
+
if (this.timedOut) {
|
|
263
|
+
const turn = this.currentDiagnosticTurn;
|
|
264
|
+
if (turn) {
|
|
265
|
+
this.logDiagnostic("unrecognized_cause", turn, {
|
|
266
|
+
cause,
|
|
267
|
+
causeReportSource,
|
|
268
|
+
receivedAt
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.unrecognizedCause = cause;
|
|
274
|
+
this.unrecognizedCauseReportSource = causeReportSource;
|
|
275
|
+
this.unrecognizedCauseReceivedAt = receivedAt;
|
|
276
|
+
}
|
|
277
|
+
acceptDiagnosticCause(cause, causeReportSource, receivedAt) {
|
|
278
|
+
var _this_acceptedCauseReceivedAt;
|
|
279
|
+
if (this.cause === null || this.cause === "unknown") {
|
|
280
|
+
this.cause = cause;
|
|
281
|
+
this.acceptedCauseReportSource = causeReportSource;
|
|
282
|
+
}
|
|
283
|
+
(_this_acceptedCauseReceivedAt = this.acceptedCauseReceivedAt) != null ? _this_acceptedCauseReceivedAt : this.acceptedCauseReceivedAt = receivedAt;
|
|
284
|
+
if (this.timedOut && !this.acceptedOutcomeLogged) {
|
|
285
|
+
const status = this.causeHoldActive ? "accepted_within_grace" : this.timeoutFlushReason === "grace_expired" ? "accepted_after_grace" : "accepted_after_timeout_flush";
|
|
286
|
+
const turn = this.currentDiagnosticTurn;
|
|
287
|
+
if (turn) {
|
|
288
|
+
this.logDiagnostic(status, turn, _extends({
|
|
289
|
+
cause,
|
|
290
|
+
causeReportSource,
|
|
291
|
+
receivedAt
|
|
292
|
+
}, status === "accepted_after_timeout_flush" && this.timeoutFlushReason ? {
|
|
293
|
+
timeoutFlushReason: this.timeoutFlushReason
|
|
294
|
+
} : {}));
|
|
295
|
+
}
|
|
296
|
+
this.acceptedOutcomeLogged = true;
|
|
297
|
+
}
|
|
298
|
+
if (this.causeHoldActive) {
|
|
299
|
+
this.causeHoldActive = false;
|
|
300
|
+
this.flushHeldTimeout();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
clientTimedOut() {
|
|
304
|
+
this.timedOut = true;
|
|
305
|
+
this.causeHoldActive = true;
|
|
306
|
+
const turn = this.currentDiagnosticTurn;
|
|
307
|
+
if (!turn) return;
|
|
308
|
+
if (this.acceptedCauseReceivedAt !== null && !this.acceptedOutcomeLogged) {
|
|
309
|
+
var _this_cause;
|
|
310
|
+
this.logDiagnostic("accepted_within_grace", turn, {
|
|
311
|
+
cause: (_this_cause = this.cause) != null ? _this_cause : undefined,
|
|
312
|
+
causeReportSource: this.acceptedCauseReportSource,
|
|
313
|
+
receivedAt: this.acceptedCauseReceivedAt
|
|
314
|
+
});
|
|
315
|
+
this.acceptedOutcomeLogged = true;
|
|
316
|
+
}
|
|
317
|
+
if (this.unrecognizedCause !== null) {
|
|
318
|
+
this.logDiagnostic("unrecognized_cause", turn, {
|
|
319
|
+
cause: this.unrecognizedCause,
|
|
320
|
+
causeReportSource: this.unrecognizedCauseReportSource,
|
|
321
|
+
receivedAt: this.unrecognizedCauseReceivedAt
|
|
322
|
+
});
|
|
323
|
+
this.unrecognizedCause = null;
|
|
324
|
+
this.unrecognizedCauseReportSource = "unavailable";
|
|
325
|
+
this.unrecognizedCauseReceivedAt = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
flushCauseHoldFor(reason) {
|
|
329
|
+
if (!this.causeHoldActive) return;
|
|
330
|
+
this.causeHoldActive = false;
|
|
331
|
+
this.timeoutFlushReason = reason;
|
|
332
|
+
if (this.cause === null) {
|
|
333
|
+
const turn = this.currentDiagnosticTurn;
|
|
334
|
+
if (turn) this.logDiagnostic("no_valid_diagnostic", turn, {
|
|
335
|
+
timeoutFlushReason: reason
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
get currentDiagnosticTurn() {
|
|
340
|
+
if (!this.clientId || this.sentAt === null) return null;
|
|
341
|
+
return {
|
|
342
|
+
clientId: this.clientId,
|
|
343
|
+
deadlineAt: this.sentAt + (/* inlined export .RESPONSE_BUDGET_MS */25000)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
logDiagnostic(diagnosticStatus, turn, param) {
|
|
347
|
+
let { cause, causeReportSource, receivedAt, receivedTextTurnClientId, timeoutFlushReason } = param === void 0 ? {} : param;
|
|
348
|
+
try {
|
|
349
|
+
this.dependencies.serverLog({
|
|
350
|
+
type: "log",
|
|
351
|
+
message: "Multimodal text turn diagnostic",
|
|
352
|
+
extra: _extends({
|
|
353
|
+
diagnostic_source: "client_timeout_attribution",
|
|
354
|
+
diagnostic_status: diagnosticStatus,
|
|
355
|
+
text_turn_client_id: turn.clientId
|
|
356
|
+
}, receivedTextTurnClientId ? {
|
|
357
|
+
received_text_turn_client_id: receivedTextTurnClientId
|
|
358
|
+
} : {}, cause ? {
|
|
359
|
+
cause
|
|
360
|
+
} : {}, causeReportSource ? {
|
|
361
|
+
cause_report_source: causeReportSource
|
|
362
|
+
} : {}, timeoutFlushReason ? {
|
|
363
|
+
timeout_flush_reason: timeoutFlushReason
|
|
364
|
+
} : {}, receivedAt !== null && receivedAt !== undefined ? {
|
|
365
|
+
delivery_lag_ms: Math.round(receivedAt - turn.deadlineAt)
|
|
366
|
+
} : {})
|
|
367
|
+
});
|
|
368
|
+
} catch (unused) {
|
|
369
|
+
// Diagnostics are best-effort and must never alter the response SLI lifecycle.
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
flushHeldTimeout() {
|
|
373
|
+
this.heldTimeoutFlush == null ? void 0 : this.heldTimeoutFlush.call(this);
|
|
374
|
+
}
|
|
375
|
+
clearCauseHold() {
|
|
376
|
+
if (this.causeHoldTimerId !== null) {
|
|
377
|
+
this.dependencies.clearTimer(this.causeHoldTimerId);
|
|
378
|
+
this.causeHoldTimerId = null;
|
|
379
|
+
}
|
|
380
|
+
this.heldTimeoutFlush = null;
|
|
381
|
+
}
|
|
382
|
+
clearResponseTimer() {
|
|
383
|
+
if (this.responseTimerId !== null) {
|
|
384
|
+
this.dependencies.clearTimer(this.responseTimerId);
|
|
385
|
+
this.responseTimerId = null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
constructor(dependencies = {}){
|
|
389
|
+
this.sentAt = null;
|
|
390
|
+
this.counterEmitted = false;
|
|
391
|
+
this.replyHandled = false;
|
|
392
|
+
this.responseTimerId = null;
|
|
393
|
+
this.causeHoldTimerId = null;
|
|
394
|
+
this.heldTimeoutFlush = null;
|
|
395
|
+
this.turnToken = 0;
|
|
396
|
+
this.clientId = null;
|
|
397
|
+
this.cause = null;
|
|
398
|
+
this.hasToolCall = false;
|
|
399
|
+
this.obscured = false;
|
|
400
|
+
this.callActive = false;
|
|
401
|
+
this.timedOut = false;
|
|
402
|
+
// True only during the 1.5-second window when the timeout metric is held
|
|
403
|
+
// so a matching backend cause can still replace `unknown`.
|
|
404
|
+
this.causeHoldActive = false;
|
|
405
|
+
// Why the client stopped waiting for a cause. Retained so a later diagnostic
|
|
406
|
+
// can distinguish normal grace expiry from a turn/lifecycle interruption.
|
|
407
|
+
this.timeoutFlushReason = null;
|
|
408
|
+
this.acceptedCauseReceivedAt = null;
|
|
409
|
+
this.acceptedCauseReportSource = "unavailable";
|
|
410
|
+
this.acceptedOutcomeLogged = false;
|
|
411
|
+
this.unrecognizedCause = null;
|
|
412
|
+
this.unrecognizedCauseReportSource = "unavailable";
|
|
413
|
+
this.unrecognizedCauseReceivedAt = null;
|
|
414
|
+
this.previousTimedOutTurn = null;
|
|
415
|
+
this.dependencies = _extends({}, defaultDependencies, dependencies);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export { TextTurnResponseTracker };
|
|
@@ -80,6 +80,26 @@ var types_ButtonShape = /*#__PURE__*/ function(ButtonShape) {
|
|
|
80
80
|
InitialDisplayMode["CLOSED"] = "closed";
|
|
81
81
|
return InitialDisplayMode;
|
|
82
82
|
}({});
|
|
83
|
+
/**
|
|
84
|
+
* Which of the two shapes the deck artifact takes. Every difference in the deck
|
|
85
|
+
* component — navigation, caption panel, zoom-follow, dot palette, surface and
|
|
86
|
+
* sizing — turns on this one discriminant, because all of them are consequences
|
|
87
|
+
* of the same fact: whose hands the deck is in.
|
|
88
|
+
*
|
|
89
|
+
* - `Guided`: Piper narrates, slides advance on their own (the decks store's
|
|
90
|
+
* timer), and she can zoom into the slide she is pointing at. Also covers a
|
|
91
|
+
* revisit, where the store hands over a single static slide with
|
|
92
|
+
* `sequenceLength: 1` — same chrome, minus the dots.
|
|
93
|
+
* - `SelfGuided`: the visitor opened the deck from its timeline card and steps
|
|
94
|
+
* through it themselves, reading the narration beneath each slide.
|
|
95
|
+
*
|
|
96
|
+
* Local to the widget, unlike the enums at the top of this file: the values
|
|
97
|
+
* cross no wire, they only name the mode and key the deck's style variants.
|
|
98
|
+
*/ var types_DeckMode = /*#__PURE__*/ function(DeckMode) {
|
|
99
|
+
DeckMode["Guided"] = "guided";
|
|
100
|
+
DeckMode["SelfGuided"] = "self_guided";
|
|
101
|
+
return DeckMode;
|
|
102
|
+
}({});
|
|
83
103
|
/**
|
|
84
104
|
* Identifies what triggered a call start so the agent can open the call with the
|
|
85
105
|
* right intent (e.g. begin a presentation) instead of a generic greeting.
|
|
@@ -102,4 +122,4 @@ var types_ButtonShape = /*#__PURE__*/ function(ButtonShape) {
|
|
|
102
122
|
}({});
|
|
103
123
|
|
|
104
124
|
var multimodal_v2_types_CallMode = /* unused export */ undefined;
|
|
105
|
-
export { multimodal_v2_types_CallMode as CallMode, types_AvatarProvider as AvatarProvider, types_ButtonShape as ButtonShape, types_ButtonStyle as ButtonStyle, types_ConversationMode as ConversationMode, types_DisplayMode as DisplayMode, types_InitialDisplayMode as InitialDisplayMode, types_InputHiddenReason as InputHiddenReason, types_InputState as InputState, types_LoadingState as LoadingState, types_MaximizedState as MaximizedState, types_OpenedDisplayMode as OpenedDisplayMode, types_StartCallTrigger as StartCallTrigger };
|
|
125
|
+
export { multimodal_v2_types_CallMode as CallMode, types_AvatarProvider as AvatarProvider, types_ButtonShape as ButtonShape, types_ButtonStyle as ButtonStyle, types_ConversationMode as ConversationMode, types_DeckMode as DeckMode, types_DisplayMode as DisplayMode, types_InitialDisplayMode as InitialDisplayMode, types_InputHiddenReason as InputHiddenReason, types_InputState as InputState, types_LoadingState as LoadingState, types_MaximizedState as MaximizedState, types_OpenedDisplayMode as OpenedDisplayMode, types_StartCallTrigger as StartCallTrigger };
|
|
@@ -7,7 +7,8 @@ import "../stores/translation_store.js";
|
|
|
7
7
|
*
|
|
8
8
|
* Our locale identifiers use underscores (they mirror the locale filenames), which the
|
|
9
9
|
* Intl constructors reject with `RangeError: Invalid language tag`. Anything handed to
|
|
10
|
-
* `Intl.*` or a `toLocale*String` call must go through this first
|
|
10
|
+
* `Intl.*` or a `toLocale*String` call must go through this first -- prefer reading
|
|
11
|
+
* `TranslationStore.effectiveIntlLocale` over calling this directly on a store locale.
|
|
11
12
|
*/ function toIntlLocale(locale) {
|
|
12
13
|
if (!locale) return (/* inlined export .DEFAULT_LOCALE */"en");
|
|
13
14
|
return locale.replace(/_/g, "-");
|