@oxvo/ai-live-assist 7.4.36 → 7.4.49
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/cjs/AiLiveAssist.d.ts +10 -1
- package/cjs/AiLiveAssist.js +797 -194
- package/cjs/context.d.ts +2 -0
- package/cjs/context.js +213 -22
- package/cjs/index.js +2 -2
- package/cjs/media.js +3 -2
- package/cjs/preview.d.ts +1 -1
- package/cjs/preview.js +2 -1
- package/cjs/types.d.ts +1 -0
- package/cjs/ui.d.ts +9 -0
- package/cjs/ui.js +478 -141
- package/cjs/version.d.ts +1 -1
- package/cjs/version.js +1 -1
- package/lib/AiLiveAssist.d.ts +10 -1
- package/lib/AiLiveAssist.js +797 -194
- package/lib/context.d.ts +2 -0
- package/lib/context.js +213 -22
- package/lib/index.js +2 -2
- package/lib/media.js +3 -2
- package/lib/preview.d.ts +1 -1
- package/lib/preview.js +2 -1
- package/lib/types.d.ts +1 -0
- package/lib/ui.d.ts +9 -0
- package/lib/ui.js +478 -141
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/cjs/AiLiveAssist.js
CHANGED
|
@@ -558,10 +558,72 @@ const versionAtLeast = (current, minimum) => {
|
|
|
558
558
|
};
|
|
559
559
|
const resolveAiGuideProtocolVersion = (bootstrap) => bootstrap.experienceMode === "voice_presence" ||
|
|
560
560
|
(bootstrap.minimumProtocolVersion ?? 1) >= 2 ||
|
|
561
|
-
bootstrap.protocol.min >= 2
|
|
561
|
+
bootstrap.protocol.min >= 2 ||
|
|
562
|
+
bootstrap.protocol.max >= 2
|
|
562
563
|
? 2
|
|
563
564
|
: 1;
|
|
564
565
|
exports.resolveAiGuideProtocolVersion = resolveAiGuideProtocolVersion;
|
|
566
|
+
const resumeProtocolIsSupported = (record, bootstrap) => {
|
|
567
|
+
const experienceMode = bootstrap.experienceMode ?? "standard";
|
|
568
|
+
if (record.experienceMode !== experienceMode)
|
|
569
|
+
return false;
|
|
570
|
+
if (record.protocolVersion === (0, exports.resolveAiGuideProtocolVersion)(bootstrap)) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
// Prefer V2 for fresh standard launches without abandoning a V1 session
|
|
574
|
+
// while the published configuration and runtime still admit V1.
|
|
575
|
+
return (record.experienceMode === "standard" &&
|
|
576
|
+
record.protocolVersion === 1 &&
|
|
577
|
+
(bootstrap.minimumProtocolVersion ?? 1) <= 1 &&
|
|
578
|
+
bootstrap.protocol.min <= 1 &&
|
|
579
|
+
bootstrap.protocol.max >= 1);
|
|
580
|
+
};
|
|
581
|
+
const protocolVersionIsAdmitted = (protocolVersion, bootstrap) => protocolVersion >= (bootstrap.minimumProtocolVersion ?? 1) &&
|
|
582
|
+
protocolVersion >= bootstrap.protocol.min &&
|
|
583
|
+
protocolVersion <= bootstrap.protocol.max;
|
|
584
|
+
const pendingLaunchIsBound = (pendingLaunch) => (pendingLaunch.protocolVersion === 1 ||
|
|
585
|
+
pendingLaunch.protocolVersion === 2) &&
|
|
586
|
+
(pendingLaunch.experienceMode === "standard" ||
|
|
587
|
+
pendingLaunch.experienceMode === "voice_presence");
|
|
588
|
+
const bindPendingLaunchIntent = (pendingLaunch, bootstrap) => {
|
|
589
|
+
const effectiveExperienceMode = bootstrap.experienceMode ?? "standard";
|
|
590
|
+
let protocolVersion = pendingLaunch.protocolVersion;
|
|
591
|
+
let experienceMode = pendingLaunch.experienceMode;
|
|
592
|
+
const hasProtocolVersion = protocolVersion === 1 || protocolVersion === 2;
|
|
593
|
+
const hasExperienceMode = experienceMode === "standard" || experienceMode === "voice_presence";
|
|
594
|
+
if (hasProtocolVersion !== hasExperienceMode)
|
|
595
|
+
return null;
|
|
596
|
+
if (!hasProtocolVersion && !hasExperienceMode) {
|
|
597
|
+
// A legacy record does not preserve its original protocol body. Current
|
|
598
|
+
// configuration can never prove the historical request, even when the
|
|
599
|
+
// present experience is voice presence, so its idempotency key must not be
|
|
600
|
+
// replayed with inferred authority.
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
if ((protocolVersion !== 1 && protocolVersion !== 2) ||
|
|
604
|
+
(experienceMode !== "standard" && experienceMode !== "voice_presence") ||
|
|
605
|
+
experienceMode !== effectiveExperienceMode ||
|
|
606
|
+
!protocolVersionIsAdmitted(protocolVersion, bootstrap) ||
|
|
607
|
+
(protocolVersion === 1 && experienceMode !== "standard") ||
|
|
608
|
+
(experienceMode === "voice_presence" &&
|
|
609
|
+
(protocolVersion !== 2 || pendingLaunch.mode !== "voice"))) {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
mode: pendingLaunch.mode,
|
|
614
|
+
scopes: [...pendingLaunch.scopes],
|
|
615
|
+
idempotencyKey: pendingLaunch.idempotencyKey,
|
|
616
|
+
protocolVersion,
|
|
617
|
+
experienceMode,
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
const freshPendingLaunchIntent = (mode, scopes, idempotencyKey, bootstrap) => ({
|
|
621
|
+
mode,
|
|
622
|
+
scopes: [...scopes],
|
|
623
|
+
idempotencyKey,
|
|
624
|
+
protocolVersion: (0, exports.resolveAiGuideProtocolVersion)(bootstrap),
|
|
625
|
+
experienceMode: bootstrap.experienceMode ?? "standard",
|
|
626
|
+
});
|
|
565
627
|
const parseTerminalEndIntent = (value) => {
|
|
566
628
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
567
629
|
return null;
|
|
@@ -596,9 +658,13 @@ const parseFreshAiReturnProvisional = (value) => {
|
|
|
596
658
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
597
659
|
return null;
|
|
598
660
|
const raw = value;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
661
|
+
const recordVersion = raw.v === 1 || raw.v === 2 ? raw.v : null;
|
|
662
|
+
const scopes = Array.isArray(raw.scopes) ? raw.scopes : [];
|
|
663
|
+
const expectedKeys = recordVersion === 1
|
|
664
|
+
? "conversationDisplayId,expiresAt,launchIdempotencyKey,linkId,mode,phase,sessionId,v"
|
|
665
|
+
: "conversationDisplayId,experienceMode,expiresAt,launchIdempotencyKey,linkId,mode,phase,protocolVersion,scopes,sessionId,v";
|
|
666
|
+
if (recordVersion === null ||
|
|
667
|
+
Object.keys(raw).sort().join(",") !== expectedKeys ||
|
|
602
668
|
!PUBLIC_REQUEST_ID_PATTERN.test(raw.linkId ?? "") ||
|
|
603
669
|
!/^[1-9]\d{0,18}$/.test(raw.conversationDisplayId ?? "") ||
|
|
604
670
|
(raw.mode !== "text" && raw.mode !== "voice") ||
|
|
@@ -613,19 +679,42 @@ const parseFreshAiReturnProvisional = (value) => {
|
|
|
613
679
|
(raw.launchIdempotencyKey === null || raw.sessionId !== null)) ||
|
|
614
680
|
(raw.phase === "launched" &&
|
|
615
681
|
(raw.launchIdempotencyKey === null || raw.sessionId === null)) ||
|
|
682
|
+
(recordVersion === 2 &&
|
|
683
|
+
raw.phase === "released" &&
|
|
684
|
+
(raw.protocolVersion !== null ||
|
|
685
|
+
raw.experienceMode !== null ||
|
|
686
|
+
raw.scopes !== null)) ||
|
|
687
|
+
(recordVersion === 2 &&
|
|
688
|
+
raw.phase !== "released" &&
|
|
689
|
+
((raw.protocolVersion !== 1 && raw.protocolVersion !== 2) ||
|
|
690
|
+
(raw.experienceMode !== "standard" &&
|
|
691
|
+
raw.experienceMode !== "voice_presence") ||
|
|
692
|
+
(raw.protocolVersion === 1 && raw.experienceMode !== "standard") ||
|
|
693
|
+
(raw.experienceMode === "voice_presence" &&
|
|
694
|
+
(raw.protocolVersion !== 2 || raw.mode !== "voice")) ||
|
|
695
|
+
!Array.isArray(raw.scopes) ||
|
|
696
|
+
scopes.length > CONSENT_SCOPES.size ||
|
|
697
|
+
scopes.some((scope) => typeof scope !== "string" ||
|
|
698
|
+
!CONSENT_SCOPES.has(scope)) ||
|
|
699
|
+
new Set(scopes).size !== scopes.length)) ||
|
|
616
700
|
!Number.isSafeInteger(raw.expiresAt) ||
|
|
617
701
|
(raw.expiresAt ?? 0) <= Date.now() ||
|
|
618
702
|
(raw.expiresAt ?? 0) > Date.now() + STORAGE_TTL_MS) {
|
|
619
703
|
return null;
|
|
620
704
|
}
|
|
621
705
|
return {
|
|
622
|
-
v:
|
|
706
|
+
v: recordVersion,
|
|
623
707
|
linkId: raw.linkId,
|
|
624
708
|
conversationDisplayId: raw.conversationDisplayId,
|
|
625
709
|
mode: raw.mode,
|
|
626
710
|
phase: raw.phase,
|
|
627
711
|
launchIdempotencyKey: raw.launchIdempotencyKey ?? null,
|
|
628
712
|
sessionId: raw.sessionId ?? null,
|
|
713
|
+
protocolVersion: recordVersion === 2 ? (raw.protocolVersion ?? null) : null,
|
|
714
|
+
experienceMode: recordVersion === 2 ? (raw.experienceMode ?? null) : null,
|
|
715
|
+
scopes: recordVersion === 2 && raw.phase !== "released"
|
|
716
|
+
? [...scopes]
|
|
717
|
+
: null,
|
|
629
718
|
expiresAt: raw.expiresAt,
|
|
630
719
|
};
|
|
631
720
|
};
|
|
@@ -662,6 +751,16 @@ const parseResumeRecord = (value) => {
|
|
|
662
751
|
const record = JSON.parse(value);
|
|
663
752
|
const scopes = Array.isArray(record.scopes) ? record.scopes : [];
|
|
664
753
|
const locale = typeof record.locale === "string" ? canonicalLocale(record.locale) : null;
|
|
754
|
+
const protocolFieldsAbsent = record.protocolVersion === undefined &&
|
|
755
|
+
record.experienceMode === undefined;
|
|
756
|
+
const protocolBindingValid = protocolFieldsAbsent ||
|
|
757
|
+
((record.protocolVersion === 1 || record.protocolVersion === 2) &&
|
|
758
|
+
(record.experienceMode === "standard" ||
|
|
759
|
+
record.experienceMode === "voice_presence") &&
|
|
760
|
+
(record.protocolVersion !== 1 ||
|
|
761
|
+
record.experienceMode === "standard") &&
|
|
762
|
+
(record.experienceMode !== "voice_presence" ||
|
|
763
|
+
(record.protocolVersion === 2 && record.mode === "voice")));
|
|
665
764
|
if (!PUBLIC_REQUEST_ID_PATTERN.test(record.sessionId ?? "") ||
|
|
666
765
|
!(0, client_js_1.isValidAssistSessionGrant)(record.resumeCredential) ||
|
|
667
766
|
!["text", "voice"].includes(record.mode ?? "") ||
|
|
@@ -670,6 +769,7 @@ const parseResumeRecord = (value) => {
|
|
|
670
769
|
scopes.some((scope) => typeof scope !== "string" ||
|
|
671
770
|
!CONSENT_SCOPES.has(scope)) ||
|
|
672
771
|
new Set(scopes).size !== scopes.length ||
|
|
772
|
+
!protocolBindingValid ||
|
|
673
773
|
!locale ||
|
|
674
774
|
typeof record.expiresAt !== "number" ||
|
|
675
775
|
!Number.isFinite(record.expiresAt) ||
|
|
@@ -903,10 +1003,10 @@ const parseResumeRecord = (value) => {
|
|
|
903
1003
|
? record.visualConsentGranted
|
|
904
1004
|
: record.scopes.includes("visual_context"),
|
|
905
1005
|
manualTurn: record.manualTurn === true,
|
|
906
|
-
protocolVersion:
|
|
907
|
-
experienceMode:
|
|
908
|
-
? "
|
|
909
|
-
:
|
|
1006
|
+
protocolVersion: protocolFieldsAbsent ? 1 : record.protocolVersion,
|
|
1007
|
+
experienceMode: protocolFieldsAbsent
|
|
1008
|
+
? "standard"
|
|
1009
|
+
: record.experienceMode,
|
|
910
1010
|
fallbackIntent: record.fallbackIntent === "standard_assist" ||
|
|
911
1011
|
record.fallbackIntent === "human_handoff"
|
|
912
1012
|
? record.fallbackIntent
|
|
@@ -985,11 +1085,62 @@ const parseTranscript = (value) => {
|
|
|
985
1085
|
}
|
|
986
1086
|
return entries;
|
|
987
1087
|
};
|
|
1088
|
+
const parsePendingLaunchIntent = (value, recordVersion) => {
|
|
1089
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1090
|
+
return null;
|
|
1091
|
+
const candidate = value;
|
|
1092
|
+
const scopes = Array.isArray(candidate.scopes) ? candidate.scopes : [];
|
|
1093
|
+
const expectedKeys = recordVersion === 1
|
|
1094
|
+
? ["idempotencyKey", "mode", "scopes"]
|
|
1095
|
+
: [
|
|
1096
|
+
"experienceMode",
|
|
1097
|
+
"idempotencyKey",
|
|
1098
|
+
"mode",
|
|
1099
|
+
"protocolVersion",
|
|
1100
|
+
"scopes",
|
|
1101
|
+
];
|
|
1102
|
+
if (canonicalJson(Object.keys(candidate).sort()) !==
|
|
1103
|
+
canonicalJson(expectedKeys) ||
|
|
1104
|
+
(candidate.mode !== "text" && candidate.mode !== "voice") ||
|
|
1105
|
+
!Array.isArray(candidate.scopes) ||
|
|
1106
|
+
typeof candidate.idempotencyKey !== "string" ||
|
|
1107
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(candidate.idempotencyKey) ||
|
|
1108
|
+
scopes.length > CONSENT_SCOPES.size ||
|
|
1109
|
+
scopes.some((scope) => typeof scope !== "string" || !CONSENT_SCOPES.has(scope)) ||
|
|
1110
|
+
new Set(scopes).size !== scopes.length) {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
const base = {
|
|
1114
|
+
mode: candidate.mode,
|
|
1115
|
+
scopes: [...scopes],
|
|
1116
|
+
idempotencyKey: candidate.idempotencyKey,
|
|
1117
|
+
};
|
|
1118
|
+
if (recordVersion === 1) {
|
|
1119
|
+
return { ...base, protocolVersion: null, experienceMode: null };
|
|
1120
|
+
}
|
|
1121
|
+
const protocolVersion = candidate.protocolVersion;
|
|
1122
|
+
const experienceMode = candidate.experienceMode;
|
|
1123
|
+
if ((protocolVersion !== 1 && protocolVersion !== 2) ||
|
|
1124
|
+
(experienceMode !== "standard" && experienceMode !== "voice_presence") ||
|
|
1125
|
+
(protocolVersion === 1 && experienceMode !== "standard") ||
|
|
1126
|
+
(experienceMode === "voice_presence" &&
|
|
1127
|
+
(protocolVersion !== 2 || candidate.mode !== "voice"))) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
return { ...base, protocolVersion, experienceMode };
|
|
1131
|
+
};
|
|
988
1132
|
const parseFocusedHandoffPayload = (value) => {
|
|
989
1133
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
990
1134
|
return null;
|
|
991
1135
|
const raw = value;
|
|
992
|
-
if (raw.v === 1) {
|
|
1136
|
+
if (raw.v === 1 || raw.v === 2) {
|
|
1137
|
+
const expectedKeys = ["resume", "transcript", "v"];
|
|
1138
|
+
if (raw.pendingLaunch !== undefined)
|
|
1139
|
+
expectedKeys.push("pendingLaunch");
|
|
1140
|
+
if (canonicalJson(Object.keys(raw).sort()) !==
|
|
1141
|
+
canonicalJson(expectedKeys.sort())) {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
993
1144
|
const resume = raw.resume === null
|
|
994
1145
|
? null
|
|
995
1146
|
: parseResumeRecord(JSON.stringify(raw.resume ?? null));
|
|
@@ -997,30 +1148,17 @@ const parseFocusedHandoffPayload = (value) => {
|
|
|
997
1148
|
return null;
|
|
998
1149
|
let pendingLaunch;
|
|
999
1150
|
if (raw.pendingLaunch !== undefined) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1151
|
+
pendingLaunch =
|
|
1152
|
+
parsePendingLaunchIntent(raw.pendingLaunch, raw.v) ?? undefined;
|
|
1153
|
+
if (resume || !pendingLaunch)
|
|
1003
1154
|
return null;
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
(candidate.mode !== "text" && candidate.mode !== "voice") ||
|
|
1009
|
-
!PUBLIC_REQUEST_ID_PATTERN.test(String(candidate.idempotencyKey ?? "")) ||
|
|
1010
|
-
scopes.length > CONSENT_SCOPES.size ||
|
|
1011
|
-
scopes.some((scope) => typeof scope !== "string" ||
|
|
1012
|
-
!CONSENT_SCOPES.has(scope)) ||
|
|
1013
|
-
new Set(scopes).size !== scopes.length) {
|
|
1014
|
-
return null;
|
|
1015
|
-
}
|
|
1016
|
-
pendingLaunch = {
|
|
1017
|
-
mode: candidate.mode,
|
|
1018
|
-
scopes: [...scopes],
|
|
1019
|
-
idempotencyKey: candidate.idempotencyKey,
|
|
1020
|
-
};
|
|
1155
|
+
}
|
|
1156
|
+
if (raw.v === 2 &&
|
|
1157
|
+
(!pendingLaunch || !pendingLaunchIsBound(pendingLaunch))) {
|
|
1158
|
+
return null;
|
|
1021
1159
|
}
|
|
1022
1160
|
return {
|
|
1023
|
-
v:
|
|
1161
|
+
v: raw.v,
|
|
1024
1162
|
resume,
|
|
1025
1163
|
transcript: parseTranscript(raw.transcript),
|
|
1026
1164
|
...(pendingLaunch ? { pendingLaunch } : {}),
|
|
@@ -1068,19 +1206,15 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1068
1206
|
const claimAuthority = raw.claimAuthority === null
|
|
1069
1207
|
? null
|
|
1070
1208
|
: parseTransferAuthority(JSON.stringify(raw.claimAuthority ?? null));
|
|
1209
|
+
const recordVersion = raw.v === 1 || raw.v === 2 ? raw.v : null;
|
|
1071
1210
|
const payload = parseFocusedHandoffPayload({
|
|
1072
|
-
v:
|
|
1211
|
+
v: recordVersion,
|
|
1073
1212
|
resume: null,
|
|
1074
1213
|
transcript: [],
|
|
1075
1214
|
pendingLaunch: raw.pendingLaunch,
|
|
1076
1215
|
});
|
|
1077
1216
|
const expiresAt = raw.expiresAt;
|
|
1078
1217
|
const topLevelKeys = Object.keys(raw).sort();
|
|
1079
|
-
const pendingKeys = raw.pendingLaunch &&
|
|
1080
|
-
typeof raw.pendingLaunch === "object" &&
|
|
1081
|
-
!Array.isArray(raw.pendingLaunch)
|
|
1082
|
-
? Object.keys(raw.pendingLaunch).sort()
|
|
1083
|
-
: [];
|
|
1084
1218
|
const authorityKeys = raw.authority &&
|
|
1085
1219
|
typeof raw.authority === "object" &&
|
|
1086
1220
|
!Array.isArray(raw.authority)
|
|
@@ -1101,14 +1235,12 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1101
1235
|
"state",
|
|
1102
1236
|
"v",
|
|
1103
1237
|
]) ||
|
|
1104
|
-
canonicalJson(pendingKeys) !==
|
|
1105
|
-
canonicalJson(["idempotencyKey", "mode", "scopes"]) ||
|
|
1106
1238
|
canonicalJson(authorityKeys) !==
|
|
1107
1239
|
canonicalJson(["ownerId", "revision", "term"]) ||
|
|
1108
1240
|
(raw.claimAuthority !== null &&
|
|
1109
1241
|
canonicalJson(claimAuthorityKeys) !==
|
|
1110
1242
|
canonicalJson(["ownerId", "revision", "term"])) ||
|
|
1111
|
-
|
|
1243
|
+
recordVersion === null ||
|
|
1112
1244
|
typeof raw.sourceOwnerId !== "string" ||
|
|
1113
1245
|
!PUBLIC_REQUEST_ID_PATTERN.test(raw.sourceOwnerId) ||
|
|
1114
1246
|
!authority ||
|
|
@@ -1132,7 +1264,7 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1132
1264
|
return null;
|
|
1133
1265
|
}
|
|
1134
1266
|
return {
|
|
1135
|
-
v:
|
|
1267
|
+
v: recordVersion,
|
|
1136
1268
|
sourceOwnerId: raw.sourceOwnerId,
|
|
1137
1269
|
authority,
|
|
1138
1270
|
state: raw.state,
|
|
@@ -1525,10 +1657,12 @@ class AiLiveAssist {
|
|
|
1525
1657
|
this.storageKey = `__oxvo_ai_live_assist_resume_${this.siteKey}`;
|
|
1526
1658
|
this.transcriptStorageKey = `__oxvo_ai_live_assist_transcript_${this.siteKey}`;
|
|
1527
1659
|
this.transferAuthorityStorageKey = `__oxvo_ai_live_assist_transfer_authority_${this.siteKey}`;
|
|
1528
|
-
this.
|
|
1660
|
+
this.legacyStagedPendingLaunchStorageKey = `__oxvo_ai_live_assist_pending_launch_${this.siteKey}`;
|
|
1661
|
+
this.stagedPendingLaunchStorageKey = `__oxvo_ai_live_assist_pending_launch_v2_${this.siteKey}`;
|
|
1529
1662
|
this.terminalEndStorageKey = `__oxvo_ai_live_assist_terminal_end_${this.siteKey}`;
|
|
1530
1663
|
this.messengerReturnEndStorageKey = `__oxvo_ai_live_assist_handoff_terminal_${this.siteKey}`;
|
|
1531
|
-
this.
|
|
1664
|
+
this.legacyFreshAiReturnProvisionalStorageKey = `__oxvo_ai_live_assist_fresh_return_${this.siteKey}`;
|
|
1665
|
+
this.freshAiReturnProvisionalStorageKey = `__oxvo_ai_live_assist_fresh_return_v2_${this.siteKey}`;
|
|
1532
1666
|
this.pageAssistanceTrackerIdentityStorageKey = `__oxvo_ai_live_assist_page_assistance_tracker_${this.siteKey}`;
|
|
1533
1667
|
this.transcript = this.readTranscript();
|
|
1534
1668
|
this.visitorIdentity = this.resolveVisitorId();
|
|
@@ -1727,8 +1861,7 @@ class AiLiveAssist {
|
|
|
1727
1861
|
pendingLaunch &&
|
|
1728
1862
|
(!staged ||
|
|
1729
1863
|
(staged.state === "in_flight" &&
|
|
1730
|
-
staged.pendingLaunch
|
|
1731
|
-
pendingLaunch.idempotencyKey))
|
|
1864
|
+
this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)))
|
|
1732
1865
|
? { ...pendingLaunch, scopes: [...pendingLaunch.scopes] }
|
|
1733
1866
|
: null;
|
|
1734
1867
|
const record = this.readResume();
|
|
@@ -1743,8 +1876,12 @@ class AiLiveAssist {
|
|
|
1743
1876
|
this.persistTerminalEndIntent(intent);
|
|
1744
1877
|
if (record)
|
|
1745
1878
|
this.storeTerminalResume(record, intent);
|
|
1746
|
-
else if (this.result)
|
|
1747
|
-
this.
|
|
1879
|
+
else if (this.result) {
|
|
1880
|
+
const retainedResult = this.result;
|
|
1881
|
+
if (!this.captureTerminalAuthorizedResult(retainedResult)) {
|
|
1882
|
+
this.rejectTerminalAuthorizedResult(retainedResult);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1748
1885
|
}
|
|
1749
1886
|
this.terminalRequestPending = true;
|
|
1750
1887
|
if (!this.relinquishModeUpgradeForRecovery(false)) {
|
|
@@ -1908,8 +2045,7 @@ class AiLiveAssist {
|
|
|
1908
2045
|
if (resume &&
|
|
1909
2046
|
!terminalIntent &&
|
|
1910
2047
|
!resumedFallback &&
|
|
1911
|
-
(resume
|
|
1912
|
-
resume.protocolVersion !== requestedProtocol ||
|
|
2048
|
+
(!resumeProtocolIsSupported(resume, bootstrap) ||
|
|
1913
2049
|
resume.fallbackIntent !== null)) {
|
|
1914
2050
|
this.clearResume();
|
|
1915
2051
|
resume = null;
|
|
@@ -2633,9 +2769,6 @@ class AiLiveAssist {
|
|
|
2633
2769
|
!this.messengerSupportsConfirmedHandoff(messenger)) {
|
|
2634
2770
|
return false;
|
|
2635
2771
|
}
|
|
2636
|
-
bridgeActivationAttempted = true;
|
|
2637
|
-
if (messenger.setAiGuideHandoffEnabled(true) !== true)
|
|
2638
|
-
return false;
|
|
2639
2772
|
if (messenger.ready) {
|
|
2640
2773
|
await this.withMessengerTimeout(messenger.ready(), MESSENGER_LOAD_TIMEOUT_MS);
|
|
2641
2774
|
}
|
|
@@ -2645,6 +2778,9 @@ class AiLiveAssist {
|
|
|
2645
2778
|
this.resolveMessenger() !== messenger) {
|
|
2646
2779
|
return false;
|
|
2647
2780
|
}
|
|
2781
|
+
bridgeActivationAttempted = true;
|
|
2782
|
+
if (messenger.setAiGuideHandoffEnabled(true) !== true)
|
|
2783
|
+
return false;
|
|
2648
2784
|
if (this.messengerInitialLauncherVisible === null) {
|
|
2649
2785
|
this.messengerInitialLauncherVisible =
|
|
2650
2786
|
messenger.launcherVisible !== false;
|
|
@@ -3873,13 +4009,16 @@ class AiLiveAssist {
|
|
|
3873
4009
|
if (expiresAt <= Date.now())
|
|
3874
4010
|
return null;
|
|
3875
4011
|
return {
|
|
3876
|
-
v:
|
|
4012
|
+
v: 2,
|
|
3877
4013
|
linkId: companion.linkId,
|
|
3878
4014
|
conversationDisplayId: conversationId,
|
|
3879
4015
|
mode,
|
|
3880
4016
|
phase: "released",
|
|
3881
4017
|
launchIdempotencyKey: null,
|
|
3882
4018
|
sessionId: null,
|
|
4019
|
+
protocolVersion: null,
|
|
4020
|
+
experienceMode: null,
|
|
4021
|
+
scopes: null,
|
|
3883
4022
|
expiresAt,
|
|
3884
4023
|
};
|
|
3885
4024
|
}
|
|
@@ -3896,8 +4035,12 @@ class AiLiveAssist {
|
|
|
3896
4035
|
}
|
|
3897
4036
|
this.persistFreshAiReturnProvisional({
|
|
3898
4037
|
...provisional,
|
|
4038
|
+
v: 2,
|
|
3899
4039
|
phase: "released",
|
|
3900
4040
|
launchIdempotencyKey: null,
|
|
4041
|
+
protocolVersion: null,
|
|
4042
|
+
experienceMode: null,
|
|
4043
|
+
scopes: null,
|
|
3901
4044
|
});
|
|
3902
4045
|
}
|
|
3903
4046
|
quiesceProvisionalFreshAiReturn() {
|
|
@@ -3911,22 +4054,59 @@ class AiLiveAssist {
|
|
|
3911
4054
|
this.view?.setPaused(true);
|
|
3912
4055
|
this.cleanupTransport();
|
|
3913
4056
|
}
|
|
4057
|
+
boundFreshAiReturnProvisional(provisional) {
|
|
4058
|
+
if (provisional.phase === "released" ||
|
|
4059
|
+
provisional.launchIdempotencyKey === null ||
|
|
4060
|
+
provisional.protocolVersion === null ||
|
|
4061
|
+
provisional.experienceMode === null ||
|
|
4062
|
+
provisional.scopes === null ||
|
|
4063
|
+
!this.bootstrap) {
|
|
4064
|
+
return null;
|
|
4065
|
+
}
|
|
4066
|
+
const pendingLaunch = {
|
|
4067
|
+
mode: provisional.mode,
|
|
4068
|
+
scopes: [...provisional.scopes],
|
|
4069
|
+
idempotencyKey: provisional.launchIdempotencyKey,
|
|
4070
|
+
protocolVersion: provisional.protocolVersion,
|
|
4071
|
+
experienceMode: provisional.experienceMode,
|
|
4072
|
+
};
|
|
4073
|
+
const bound = bindPendingLaunchIntent(pendingLaunch, this.bootstrap);
|
|
4074
|
+
return bound && this.pendingLaunchMatches(bound, pendingLaunch)
|
|
4075
|
+
? bound
|
|
4076
|
+
: null;
|
|
4077
|
+
}
|
|
4078
|
+
resumeMatchesFreshAiReturn(resume, pendingLaunch) {
|
|
4079
|
+
return (resume.protocolVersion === pendingLaunch.protocolVersion &&
|
|
4080
|
+
resume.experienceMode === pendingLaunch.experienceMode &&
|
|
4081
|
+
resume.mode === pendingLaunch.mode &&
|
|
4082
|
+
canonicalJson(resume.scopes) === canonicalJson(pendingLaunch.scopes));
|
|
4083
|
+
}
|
|
4084
|
+
resultMatchesFreshAiReturn(result, pendingLaunch) {
|
|
4085
|
+
return (result.schemaVersion === pendingLaunch.protocolVersion &&
|
|
4086
|
+
result.protocolVersion === pendingLaunch.protocolVersion &&
|
|
4087
|
+
result.experienceMode === pendingLaunch.experienceMode &&
|
|
4088
|
+
(result.mode === undefined || result.mode === pendingLaunch.mode));
|
|
4089
|
+
}
|
|
3914
4090
|
async retireProvisionalFreshAiReturn(provisional) {
|
|
3915
4091
|
if (provisional.phase === "released")
|
|
3916
4092
|
return true;
|
|
4093
|
+
const pendingLaunch = this.boundFreshAiReturnProvisional(provisional);
|
|
4094
|
+
if (!pendingLaunch)
|
|
4095
|
+
return false;
|
|
3917
4096
|
const resume = this.readResume();
|
|
3918
4097
|
const result = this.result;
|
|
3919
4098
|
const sessionId = provisional.sessionId ?? resume?.sessionId ?? result?.sessionId ?? null;
|
|
3920
4099
|
if (!sessionId)
|
|
3921
4100
|
return false;
|
|
3922
|
-
const authority = resume?.sessionId === sessionId
|
|
4101
|
+
const authority = resume?.sessionId === sessionId &&
|
|
4102
|
+
this.resumeMatchesFreshAiReturn(resume, pendingLaunch)
|
|
3923
4103
|
? {
|
|
3924
4104
|
resumeCredential: resume.resumeCredential,
|
|
3925
4105
|
protocolVersion: resume.protocolVersion,
|
|
3926
4106
|
}
|
|
3927
4107
|
: result?.sessionId === sessionId &&
|
|
3928
4108
|
typeof result.resumeCredential === "string" &&
|
|
3929
|
-
(result
|
|
4109
|
+
this.resultMatchesFreshAiReturn(result, pendingLaunch)
|
|
3930
4110
|
? {
|
|
3931
4111
|
resumeCredential: result.resumeCredential,
|
|
3932
4112
|
protocolVersion: result.protocolVersion,
|
|
@@ -3984,9 +4164,13 @@ class AiLiveAssist {
|
|
|
3984
4164
|
provisional.conversationDisplayId;
|
|
3985
4165
|
const released = {
|
|
3986
4166
|
...provisional,
|
|
4167
|
+
v: 2,
|
|
3987
4168
|
phase: "released",
|
|
3988
4169
|
launchIdempotencyKey: null,
|
|
3989
4170
|
sessionId: null,
|
|
4171
|
+
protocolVersion: null,
|
|
4172
|
+
experienceMode: null,
|
|
4173
|
+
scopes: null,
|
|
3990
4174
|
};
|
|
3991
4175
|
return this.persistFreshAiReturnProvisional(released);
|
|
3992
4176
|
}
|
|
@@ -4007,6 +4191,46 @@ class AiLiveAssist {
|
|
|
4007
4191
|
// interrupted must never be replaced by a second fresh operation.
|
|
4008
4192
|
return provisional.phase === "launching" ? "launch" : "blocked";
|
|
4009
4193
|
}
|
|
4194
|
+
if (provisional.protocolVersion === null ||
|
|
4195
|
+
provisional.experienceMode === null ||
|
|
4196
|
+
provisional.scopes === null) {
|
|
4197
|
+
if (provisional.launchIdempotencyKey === null ||
|
|
4198
|
+
!resume ||
|
|
4199
|
+
resume.sessionId !== sessionId) {
|
|
4200
|
+
return "blocked";
|
|
4201
|
+
}
|
|
4202
|
+
const upgraded = {
|
|
4203
|
+
...provisional,
|
|
4204
|
+
v: 2,
|
|
4205
|
+
sessionId,
|
|
4206
|
+
protocolVersion: resume.protocolVersion,
|
|
4207
|
+
experienceMode: resume.experienceMode,
|
|
4208
|
+
scopes: [...resume.scopes],
|
|
4209
|
+
};
|
|
4210
|
+
if (!this.boundFreshAiReturnProvisional(upgraded) ||
|
|
4211
|
+
!this.persistFreshAiReturnProvisional(upgraded)) {
|
|
4212
|
+
return "blocked";
|
|
4213
|
+
}
|
|
4214
|
+
provisional = upgraded;
|
|
4215
|
+
}
|
|
4216
|
+
const pendingLaunch = this.boundFreshAiReturnProvisional(provisional);
|
|
4217
|
+
if (!pendingLaunch)
|
|
4218
|
+
return "blocked";
|
|
4219
|
+
let hasSessionAuthority = false;
|
|
4220
|
+
if (resume?.sessionId === sessionId) {
|
|
4221
|
+
if (!this.resumeMatchesFreshAiReturn(resume, pendingLaunch)) {
|
|
4222
|
+
return "blocked";
|
|
4223
|
+
}
|
|
4224
|
+
hasSessionAuthority = true;
|
|
4225
|
+
}
|
|
4226
|
+
if (this.result?.sessionId === sessionId) {
|
|
4227
|
+
if (!this.resultMatchesFreshAiReturn(this.result, pendingLaunch)) {
|
|
4228
|
+
return "blocked";
|
|
4229
|
+
}
|
|
4230
|
+
hasSessionAuthority = true;
|
|
4231
|
+
}
|
|
4232
|
+
if (!hasSessionAuthority)
|
|
4233
|
+
return "blocked";
|
|
4010
4234
|
if (provisional.phase === "launching") {
|
|
4011
4235
|
provisional = {
|
|
4012
4236
|
...provisional,
|
|
@@ -4208,17 +4432,61 @@ class AiLiveAssist {
|
|
|
4208
4432
|
const currentProvisional = this.freshAiReturnProvisionalFor(conversationId);
|
|
4209
4433
|
if (!currentProvisional)
|
|
4210
4434
|
return false;
|
|
4435
|
+
if (currentProvisional.phase === "launching" &&
|
|
4436
|
+
currentProvisional.mode !== mode) {
|
|
4437
|
+
return false;
|
|
4438
|
+
}
|
|
4439
|
+
const persistedLaunchScopes = currentProvisional.phase === "launching"
|
|
4440
|
+
? currentProvisional.scopes
|
|
4441
|
+
: null;
|
|
4442
|
+
if (currentProvisional.phase === "launching" &&
|
|
4443
|
+
(currentProvisional.protocolVersion === null ||
|
|
4444
|
+
currentProvisional.experienceMode === null ||
|
|
4445
|
+
persistedLaunchScopes === null)) {
|
|
4446
|
+
// A legacy Fresh-return record does not contain the complete request
|
|
4447
|
+
// body. Reusing its idempotency key with current bootstrap scopes or
|
|
4448
|
+
// protocol negotiation would create a different operation.
|
|
4449
|
+
return false;
|
|
4450
|
+
}
|
|
4211
4451
|
const launchIdempotencyKey = currentProvisional.phase === "launching"
|
|
4212
4452
|
? currentProvisional.launchIdempotencyKey
|
|
4213
4453
|
: (0, client_js_1.idempotencyKey)("launch");
|
|
4214
4454
|
if (!launchIdempotencyKey)
|
|
4215
4455
|
return false;
|
|
4456
|
+
const launchScopes = persistedLaunchScopes
|
|
4457
|
+
? [...persistedLaunchScopes]
|
|
4458
|
+
: [...scopes];
|
|
4459
|
+
const provisionalPendingLaunch = currentProvisional.protocolVersion !== null &&
|
|
4460
|
+
currentProvisional.experienceMode !== null
|
|
4461
|
+
? {
|
|
4462
|
+
mode: currentProvisional.mode,
|
|
4463
|
+
scopes: launchScopes,
|
|
4464
|
+
idempotencyKey: launchIdempotencyKey,
|
|
4465
|
+
protocolVersion: currentProvisional.protocolVersion,
|
|
4466
|
+
experienceMode: currentProvisional.experienceMode,
|
|
4467
|
+
}
|
|
4468
|
+
: {
|
|
4469
|
+
mode: currentProvisional.mode,
|
|
4470
|
+
scopes: launchScopes,
|
|
4471
|
+
idempotencyKey: launchIdempotencyKey,
|
|
4472
|
+
protocolVersion: null,
|
|
4473
|
+
experienceMode: null,
|
|
4474
|
+
};
|
|
4475
|
+
const pendingLaunch = currentProvisional.phase === "launching"
|
|
4476
|
+
? bindPendingLaunchIntent(provisionalPendingLaunch, bootstrap)
|
|
4477
|
+
: freshPendingLaunchIntent(mode, scopes, launchIdempotencyKey, bootstrap);
|
|
4478
|
+
if (!pendingLaunch)
|
|
4479
|
+
return false;
|
|
4216
4480
|
const launching = {
|
|
4217
4481
|
...currentProvisional,
|
|
4218
|
-
|
|
4482
|
+
v: 2,
|
|
4483
|
+
mode: pendingLaunch.mode,
|
|
4219
4484
|
phase: "launching",
|
|
4220
4485
|
launchIdempotencyKey,
|
|
4221
4486
|
sessionId: null,
|
|
4487
|
+
protocolVersion: pendingLaunch.protocolVersion,
|
|
4488
|
+
experienceMode: pendingLaunch.experienceMode,
|
|
4489
|
+
scopes: [...pendingLaunch.scopes],
|
|
4222
4490
|
};
|
|
4223
4491
|
if (!this.persistFreshAiReturnProvisional(launching))
|
|
4224
4492
|
return false;
|
|
@@ -4234,7 +4502,7 @@ class AiLiveAssist {
|
|
|
4234
4502
|
inboxId: messengerDescriptor.inboxId,
|
|
4235
4503
|
linkId: this.companion.linkId,
|
|
4236
4504
|
launchIdempotencyKey,
|
|
4237
|
-
mode,
|
|
4505
|
+
mode: pendingLaunch.mode,
|
|
4238
4506
|
previousAiSessionId: this.companion.currentAiSessionId,
|
|
4239
4507
|
surface: this.messengerTransferredSurface,
|
|
4240
4508
|
}
|
|
@@ -4242,10 +4510,11 @@ class AiLiveAssist {
|
|
|
4242
4510
|
if (!messengerReturnAuthority)
|
|
4243
4511
|
return false;
|
|
4244
4512
|
this.messengerReturnRetryRemaining = 1;
|
|
4245
|
-
await this.launch(mode, scopes, launchIdempotencyKey, {
|
|
4246
|
-
startMuted: mode === "voice",
|
|
4513
|
+
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, launchIdempotencyKey, {
|
|
4514
|
+
startMuted: pendingLaunch.mode === "voice",
|
|
4247
4515
|
associateCompanion: false,
|
|
4248
4516
|
messengerReturnAuthority,
|
|
4517
|
+
pendingLaunch,
|
|
4249
4518
|
});
|
|
4250
4519
|
this.messengerReturnRetryRemaining = null;
|
|
4251
4520
|
const sessionId = this.result?.sessionId;
|
|
@@ -4440,6 +4709,7 @@ class AiLiveAssist {
|
|
|
4440
4709
|
if (this.pushActive)
|
|
4441
4710
|
this.media?.beginPushToTalk();
|
|
4442
4711
|
this.closeMessengerBeforeOpen();
|
|
4712
|
+
await this.prepareMessengerIntegration();
|
|
4443
4713
|
return true;
|
|
4444
4714
|
}
|
|
4445
4715
|
async associateFreshCompanionSession(mode, sessionId) {
|
|
@@ -4563,11 +4833,6 @@ class AiLiveAssist {
|
|
|
4563
4833
|
const prepared = await this.withMessengerTimeout(messenger.openHandoffConversation(displayId, options), MESSENGER_LOAD_TIMEOUT_MS);
|
|
4564
4834
|
if (!prepared || !surfaceCurrent())
|
|
4565
4835
|
return;
|
|
4566
|
-
const returnActivated = await this.createMessengerReturnActivationPromise(messenger, displayId);
|
|
4567
|
-
if (!returnActivated || !surfaceCurrent()) {
|
|
4568
|
-
this.closeMessengerBeforeOpen();
|
|
4569
|
-
return;
|
|
4570
|
-
}
|
|
4571
4836
|
const switched = await this.requestCompanionSurface("messenger", mode, (0, client_js_1.idempotencyKey)("companion_switch"), result.sessionId);
|
|
4572
4837
|
const switchedCurrent = () => generation === this.sessionEffectGeneration &&
|
|
4573
4838
|
this.result === result &&
|
|
@@ -4635,6 +4900,11 @@ class AiLiveAssist {
|
|
|
4635
4900
|
await this.restoreAiAfterFailedSupportSwitch(mode, result.sessionId, previousMuted, previousPushActive);
|
|
4636
4901
|
return;
|
|
4637
4902
|
}
|
|
4903
|
+
const returnActivated = await this.createMessengerReturnActivationPromise(messenger, displayId);
|
|
4904
|
+
if (!returnActivated || !switchedCurrent()) {
|
|
4905
|
+
await this.restoreAiAfterFailedSupportSwitch(mode, result.sessionId, previousMuted, previousPushActive);
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4638
4908
|
this.messenger = messenger;
|
|
4639
4909
|
this.messengerSurfaceActive = true;
|
|
4640
4910
|
this.messengerTransferredConversationId = displayId;
|
|
@@ -6197,9 +6467,27 @@ class AiLiveAssist {
|
|
|
6197
6467
|
releaseMessengerReturnIfPreRuntime();
|
|
6198
6468
|
return;
|
|
6199
6469
|
}
|
|
6200
|
-
const
|
|
6470
|
+
const retryIntent = options.pendingLaunch;
|
|
6471
|
+
if (retryIntent &&
|
|
6472
|
+
canonicalJson({ mode, scopes, idempotencyKey: launchIdempotencyKey }) !==
|
|
6473
|
+
canonicalJson({
|
|
6474
|
+
mode: retryIntent.mode,
|
|
6475
|
+
scopes: retryIntent.scopes,
|
|
6476
|
+
idempotencyKey: retryIntent.idempotencyKey,
|
|
6477
|
+
})) {
|
|
6478
|
+
releaseMessengerReturnIfPreRuntime();
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6481
|
+
let durableLaunch = retryIntent
|
|
6482
|
+
? bindPendingLaunchIntent(retryIntent, bootstrap)
|
|
6483
|
+
: freshPendingLaunchIntent(mode, scopes, launchIdempotencyKey, bootstrap);
|
|
6484
|
+
if (!durableLaunch) {
|
|
6485
|
+
this.view.showError(this.view.message("reconnectFailed"), false, false);
|
|
6486
|
+
releaseMessengerReturnIfPreRuntime();
|
|
6487
|
+
return;
|
|
6488
|
+
}
|
|
6489
|
+
const { experienceMode, protocolVersion } = durableLaunch;
|
|
6201
6490
|
const voicePresence = experienceMode === "voice_presence";
|
|
6202
|
-
const protocolVersion = (0, exports.resolveAiGuideProtocolVersion)(bootstrap);
|
|
6203
6491
|
if (voicePresence && mode !== "voice") {
|
|
6204
6492
|
this.view.showError("Voice assist requires microphone access.", false, false);
|
|
6205
6493
|
releaseMessengerReturnIfPreRuntime();
|
|
@@ -6220,11 +6508,7 @@ class AiLiveAssist {
|
|
|
6220
6508
|
this.visitorActiveTakeoverOnly = false;
|
|
6221
6509
|
}
|
|
6222
6510
|
if (this.visitorActiveTakeoverOnly) {
|
|
6223
|
-
const pendingLaunch =
|
|
6224
|
-
mode,
|
|
6225
|
-
scopes: [...scopes],
|
|
6226
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6227
|
-
};
|
|
6511
|
+
const pendingLaunch = durableLaunch;
|
|
6228
6512
|
this.lastLaunch = pendingLaunch;
|
|
6229
6513
|
this.view.showActiveElsewhere(false);
|
|
6230
6514
|
this.view.showConnecting(true, mode);
|
|
@@ -6238,11 +6522,7 @@ class AiLiveAssist {
|
|
|
6238
6522
|
}
|
|
6239
6523
|
this.pageAssistanceAuthorityGeneration += 1;
|
|
6240
6524
|
this.rotatePageAssistanceBindingGeneration();
|
|
6241
|
-
this.lastLaunch = {
|
|
6242
|
-
mode,
|
|
6243
|
-
scopes: [...scopes],
|
|
6244
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6245
|
-
};
|
|
6525
|
+
this.lastLaunch = { ...durableLaunch, scopes: [...durableLaunch.scopes] };
|
|
6246
6526
|
this.launchInFlight = true;
|
|
6247
6527
|
if (!this.lock.acquire()) {
|
|
6248
6528
|
this.view.showActiveElsewhere(false);
|
|
@@ -6300,7 +6580,8 @@ class AiLiveAssist {
|
|
|
6300
6580
|
if (voicePresence ||
|
|
6301
6581
|
mode !== "voice" ||
|
|
6302
6582
|
!bootstrap.capabilities?.text ||
|
|
6303
|
-
options.messengerReturnAuthority !== undefined
|
|
6583
|
+
options.messengerReturnAuthority !== undefined ||
|
|
6584
|
+
retryIntent !== undefined) {
|
|
6304
6585
|
throw error;
|
|
6305
6586
|
}
|
|
6306
6587
|
this.releaseMedia(media);
|
|
@@ -6330,22 +6611,20 @@ class AiLiveAssist {
|
|
|
6330
6611
|
this.navigationConsentResetCandidate = null;
|
|
6331
6612
|
this.navigationConsentReset = null;
|
|
6332
6613
|
this.navigationConsentResetReady = null;
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6614
|
+
if (!retryIntent) {
|
|
6615
|
+
durableLaunch = {
|
|
6616
|
+
...durableLaunch,
|
|
6617
|
+
mode: activeMode,
|
|
6618
|
+
scopes: [...activeScopes],
|
|
6619
|
+
};
|
|
6620
|
+
}
|
|
6621
|
+
this.lastLaunch = { ...durableLaunch, scopes: [...durableLaunch.scopes] };
|
|
6338
6622
|
this.visualConsentGranted = activeScopes.includes("visual_context");
|
|
6339
6623
|
assertConnectionCurrent();
|
|
6340
6624
|
if (this.lock.ownsLatestFocus?.() === false &&
|
|
6341
6625
|
this.relinquishForNewerFocus()) {
|
|
6342
6626
|
return;
|
|
6343
6627
|
}
|
|
6344
|
-
const durableLaunch = {
|
|
6345
|
-
mode: activeMode,
|
|
6346
|
-
scopes: [...activeScopes],
|
|
6347
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6348
|
-
};
|
|
6349
6628
|
if (!this.markStagedPendingLaunchInFlight(durableLaunch)) {
|
|
6350
6629
|
this.releaseMedia(media);
|
|
6351
6630
|
this.cleanupTransport();
|
|
@@ -6390,16 +6669,19 @@ class AiLiveAssist {
|
|
|
6390
6669
|
}
|
|
6391
6670
|
}
|
|
6392
6671
|
}
|
|
6672
|
+
if (result.schemaVersion !== protocolVersion ||
|
|
6673
|
+
result.protocolVersion !== protocolVersion ||
|
|
6674
|
+
result.experienceMode !== experienceMode) {
|
|
6675
|
+
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6676
|
+
}
|
|
6393
6677
|
if (this.terminalRequestPending) {
|
|
6394
|
-
this.captureTerminalAuthorizedResult(result)
|
|
6678
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
6679
|
+
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6680
|
+
}
|
|
6395
6681
|
runtimeSessionCreated = true;
|
|
6396
6682
|
return;
|
|
6397
6683
|
}
|
|
6398
6684
|
assertConnectionCurrent();
|
|
6399
|
-
if (result.experienceMode !== experienceMode ||
|
|
6400
|
-
result.protocolVersion !== protocolVersion) {
|
|
6401
|
-
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6402
|
-
}
|
|
6403
6685
|
if (!this.commitAuthorizedRuntimePath(requestedLaunchPath, true)) {
|
|
6404
6686
|
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime authorized an invalid page path.", false, null, 502);
|
|
6405
6687
|
}
|
|
@@ -6534,7 +6816,8 @@ class AiLiveAssist {
|
|
|
6534
6816
|
this.terminalRequestPending ||
|
|
6535
6817
|
this.ended ||
|
|
6536
6818
|
this.destroyed ||
|
|
6537
|
-
this.lastLaunch
|
|
6819
|
+
!this.lastLaunch ||
|
|
6820
|
+
!this.pendingLaunchMatches(this.lastLaunch, pendingLaunch) ||
|
|
6538
6821
|
this.readResume()) {
|
|
6539
6822
|
return;
|
|
6540
6823
|
}
|
|
@@ -6566,7 +6849,8 @@ class AiLiveAssist {
|
|
|
6566
6849
|
this.visitorActiveEligibilityRecheckKey = pendingLaunch.idempotencyKey;
|
|
6567
6850
|
const pendingIntentIsCurrent = () => launchGeneration === this.launchGeneration &&
|
|
6568
6851
|
this.visitorActiveEligibilityRecheckController === controller &&
|
|
6569
|
-
this.lastLaunch
|
|
6852
|
+
Boolean(this.lastLaunch &&
|
|
6853
|
+
this.pendingLaunchMatches(this.lastLaunch, pendingLaunch)) &&
|
|
6570
6854
|
!this.terminalRequestPending &&
|
|
6571
6855
|
!this.ended &&
|
|
6572
6856
|
!this.destroyed &&
|
|
@@ -6626,7 +6910,7 @@ class AiLiveAssist {
|
|
|
6626
6910
|
this.visitorActiveTakeoverOnly = false;
|
|
6627
6911
|
this.lastLaunch = null;
|
|
6628
6912
|
this.view?.showActiveElsewhere(false);
|
|
6629
|
-
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, pendingLaunch.idempotencyKey);
|
|
6913
|
+
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, pendingLaunch.idempotencyKey, { pendingLaunch });
|
|
6630
6914
|
return;
|
|
6631
6915
|
}
|
|
6632
6916
|
if (status === "visitor_limited") {
|
|
@@ -6658,7 +6942,8 @@ class AiLiveAssist {
|
|
|
6658
6942
|
this.visitorActiveEligibilityRecheck = operation;
|
|
6659
6943
|
}
|
|
6660
6944
|
finishVisitorActiveEligibilityRecheck(pendingLaunch, messageKey) {
|
|
6661
|
-
if (this.lastLaunch
|
|
6945
|
+
if (!this.lastLaunch ||
|
|
6946
|
+
!this.pendingLaunchMatches(this.lastLaunch, pendingLaunch)) {
|
|
6662
6947
|
return;
|
|
6663
6948
|
}
|
|
6664
6949
|
this.lastLaunch = null;
|
|
@@ -6815,8 +7100,15 @@ class AiLiveAssist {
|
|
|
6815
7100
|
}
|
|
6816
7101
|
}
|
|
6817
7102
|
}
|
|
7103
|
+
if (result.schemaVersion !== record.protocolVersion ||
|
|
7104
|
+
result.protocolVersion !== record.protocolVersion ||
|
|
7105
|
+
result.experienceMode !== record.experienceMode) {
|
|
7106
|
+
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The saved voice assist session is no longer compatible.", false, null, 409);
|
|
7107
|
+
}
|
|
6818
7108
|
if (this.terminalRequestPending) {
|
|
6819
|
-
this.captureTerminalAuthorizedResult(result, record)
|
|
7109
|
+
if (!this.captureTerminalAuthorizedResult(result, record)) {
|
|
7110
|
+
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The saved voice assist session is no longer compatible.", false, null, 409);
|
|
7111
|
+
}
|
|
6820
7112
|
if (modeUpgrade)
|
|
6821
7113
|
this.modeUpgradeTerminalResult = result;
|
|
6822
7114
|
attemptedCredentials.add(result.resumeCredential);
|
|
@@ -6826,8 +7118,6 @@ class AiLiveAssist {
|
|
|
6826
7118
|
return;
|
|
6827
7119
|
assertConnectionCurrent();
|
|
6828
7120
|
if (result.sessionId !== record.sessionId ||
|
|
6829
|
-
result.protocolVersion !== record.protocolVersion ||
|
|
6830
|
-
result.experienceMode !== record.experienceMode ||
|
|
6831
7121
|
(result.mode !== undefined &&
|
|
6832
7122
|
result.mode !== "text" &&
|
|
6833
7123
|
result.mode !== "voice") ||
|
|
@@ -7358,6 +7648,7 @@ class AiLiveAssist {
|
|
|
7358
7648
|
? null
|
|
7359
7649
|
: (result.assistant?.welcomeMessage ??
|
|
7360
7650
|
this.bootstrap?.appearance?.welcomeMessage);
|
|
7651
|
+
let renderedPendingTextQuestion = false;
|
|
7361
7652
|
if (mode === "text" &&
|
|
7362
7653
|
this.pendingQuestion &&
|
|
7363
7654
|
!this.pendingQuestion.shown) {
|
|
@@ -7365,8 +7656,11 @@ class AiLiveAssist {
|
|
|
7365
7656
|
this.pendingQuestion.shown = true;
|
|
7366
7657
|
this.rememberTranscript(this.pendingQuestion.submissionId, "user", this.pendingQuestion.text);
|
|
7367
7658
|
(this.renderedTranscriptIds ?? (this.renderedTranscriptIds = new Set())).add(this.pendingQuestion.submissionId);
|
|
7659
|
+
renderedPendingTextQuestion = true;
|
|
7368
7660
|
}
|
|
7369
|
-
const visibleWelcome =
|
|
7661
|
+
const visibleWelcome = resume ||
|
|
7662
|
+
renderedPendingTextQuestion ||
|
|
7663
|
+
this.transcriptRestoredFromCache ||
|
|
7370
7664
|
(this.view instanceof voicePresenceUi_js_1.VoicePresenceView && mode === "voice")
|
|
7371
7665
|
? null
|
|
7372
7666
|
: welcomeMessage;
|
|
@@ -13101,7 +13395,7 @@ class AiLiveAssist {
|
|
|
13101
13395
|
return;
|
|
13102
13396
|
}
|
|
13103
13397
|
this.messengerReturnRetryRemaining -= 1;
|
|
13104
|
-
const retryAttempt = this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey);
|
|
13398
|
+
const retryAttempt = this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey, { pendingLaunch: this.lastLaunch });
|
|
13105
13399
|
return retryAttempt.finally(() => {
|
|
13106
13400
|
if (this.result && !this.ended) {
|
|
13107
13401
|
this.messengerReturnRetryRemaining = null;
|
|
@@ -13121,7 +13415,7 @@ class AiLiveAssist {
|
|
|
13121
13415
|
return this.resume(resume);
|
|
13122
13416
|
}
|
|
13123
13417
|
if (this.lastLaunch) {
|
|
13124
|
-
return this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey);
|
|
13418
|
+
return this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey, { pendingLaunch: this.lastLaunch });
|
|
13125
13419
|
}
|
|
13126
13420
|
}
|
|
13127
13421
|
scheduleReconnect(force = false) {
|
|
@@ -13451,6 +13745,18 @@ class AiLiveAssist {
|
|
|
13451
13745
|
canonicalJson(left.messengerBinding) ===
|
|
13452
13746
|
canonicalJson(right.messengerBinding));
|
|
13453
13747
|
}
|
|
13748
|
+
sameResumeAuthorityIdentity(left, right) {
|
|
13749
|
+
const credentialRotated = left.resumeCredential !== right.resumeCredential;
|
|
13750
|
+
if (!credentialRotated && left.expiresAt !== right.expiresAt)
|
|
13751
|
+
return false;
|
|
13752
|
+
const { resumeCredential: _leftCredential, expiresAt: _leftCredentialExpiry, ...leftAuthority } = left;
|
|
13753
|
+
const { resumeCredential: _rightCredential, expiresAt: _rightCredentialExpiry, ...rightAuthority } = right;
|
|
13754
|
+
void _leftCredential;
|
|
13755
|
+
void _rightCredential;
|
|
13756
|
+
void _leftCredentialExpiry;
|
|
13757
|
+
void _rightCredentialExpiry;
|
|
13758
|
+
return canonicalJson(leftAuthority) === canonicalJson(rightAuthority);
|
|
13759
|
+
}
|
|
13454
13760
|
sameModeUpgradeIntent(left, right) {
|
|
13455
13761
|
return (left?.requestId === right?.requestId &&
|
|
13456
13762
|
left?.resumeIdempotencyKey === right?.resumeIdempotencyKey &&
|
|
@@ -13819,10 +14125,22 @@ class AiLiveAssist {
|
|
|
13819
14125
|
this.state("ended");
|
|
13820
14126
|
}
|
|
13821
14127
|
captureTerminalAuthorizedResult(result, sourceRecord = null) {
|
|
13822
|
-
|
|
13823
|
-
(
|
|
13824
|
-
|
|
13825
|
-
|
|
14128
|
+
const pendingLaunch = this.terminalPendingLaunch &&
|
|
14129
|
+
pendingLaunchIsBound(this.terminalPendingLaunch)
|
|
14130
|
+
? this.terminalPendingLaunch
|
|
14131
|
+
: null;
|
|
14132
|
+
const expectedProtocolVersion = sourceRecord?.protocolVersion ??
|
|
14133
|
+
pendingLaunch?.protocolVersion ??
|
|
14134
|
+
result.protocolVersion;
|
|
14135
|
+
const expectedExperienceMode = sourceRecord?.experienceMode ??
|
|
14136
|
+
pendingLaunch?.experienceMode ??
|
|
14137
|
+
result.experienceMode;
|
|
14138
|
+
if (result.schemaVersion !== expectedProtocolVersion ||
|
|
14139
|
+
result.protocolVersion !== expectedProtocolVersion ||
|
|
14140
|
+
result.experienceMode !== expectedExperienceMode ||
|
|
14141
|
+
(sourceRecord &&
|
|
14142
|
+
(result.sessionId !== sourceRecord.sessionId ||
|
|
14143
|
+
result.protocolVersion !== sourceRecord.protocolVersion))) {
|
|
13826
14144
|
return false;
|
|
13827
14145
|
}
|
|
13828
14146
|
const currentIntent = this.terminalEndIntent;
|
|
@@ -13848,6 +14166,22 @@ class AiLiveAssist {
|
|
|
13848
14166
|
}
|
|
13849
14167
|
return true;
|
|
13850
14168
|
}
|
|
14169
|
+
retryRejectedTerminalAuthorizedResult(result) {
|
|
14170
|
+
const error = this.rejectTerminalAuthorizedResult(result);
|
|
14171
|
+
return this.retryTerminalEnd(error);
|
|
14172
|
+
}
|
|
14173
|
+
rejectTerminalAuthorizedResult(result) {
|
|
14174
|
+
if (this.result === result)
|
|
14175
|
+
this.result = null;
|
|
14176
|
+
if (this.modeUpgradeTerminalResult === result) {
|
|
14177
|
+
this.modeUpgradeTerminalResult = null;
|
|
14178
|
+
}
|
|
14179
|
+
this.terminalSessionAuthorization = null;
|
|
14180
|
+
this.terminalSessionAuthorizationController = null;
|
|
14181
|
+
const error = new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The assist runtime returned an incompatible terminal session.", false, null, 409);
|
|
14182
|
+
this.terminalSessionAuthorizationFailure = error;
|
|
14183
|
+
return error;
|
|
14184
|
+
}
|
|
13851
14185
|
completeRuntimeEnd() {
|
|
13852
14186
|
if (this.ended)
|
|
13853
14187
|
return Promise.resolve();
|
|
@@ -13878,8 +14212,9 @@ class AiLiveAssist {
|
|
|
13878
14212
|
if (terminalAuthorization) {
|
|
13879
14213
|
return this.waitForTerminalRequestDeadline(terminalAuthorization, this.terminalSessionAuthorizationController).then((authorized) => {
|
|
13880
14214
|
const result = authorized ?? this.result ?? this.modeUpgradeTerminalResult;
|
|
13881
|
-
if (result
|
|
13882
|
-
this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)
|
|
14215
|
+
if (result &&
|
|
14216
|
+
!this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)) {
|
|
14217
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
13883
14218
|
}
|
|
13884
14219
|
return this.finishRuntimeEndAttempt(result);
|
|
13885
14220
|
}, (error) => {
|
|
@@ -13896,7 +14231,9 @@ class AiLiveAssist {
|
|
|
13896
14231
|
}
|
|
13897
14232
|
if (this.terminalPendingLaunch) {
|
|
13898
14233
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13899
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14234
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14235
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14236
|
+
}
|
|
13900
14237
|
return this.finishRuntimeEndAttempt(result);
|
|
13901
14238
|
}, (recoveryError) => this.retryTerminalEnd(recoveryError));
|
|
13902
14239
|
}
|
|
@@ -13923,7 +14260,9 @@ class AiLiveAssist {
|
|
|
13923
14260
|
return this.finishRuntimeEndAttempt(null);
|
|
13924
14261
|
if (this.terminalPendingLaunch) {
|
|
13925
14262
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13926
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14263
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14264
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14265
|
+
}
|
|
13927
14266
|
return this.finishRuntimeEndAttempt(result);
|
|
13928
14267
|
}, (error) => this.retryTerminalEnd(error));
|
|
13929
14268
|
}
|
|
@@ -14080,13 +14419,21 @@ class AiLiveAssist {
|
|
|
14080
14419
|
requestTerminalPendingLaunch(pendingLaunch) {
|
|
14081
14420
|
const intent = this.terminalEndIntent;
|
|
14082
14421
|
if (!intent ||
|
|
14083
|
-
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey
|
|
14084
|
-
!this.markStagedPendingLaunchInFlight(pendingLaunch)) {
|
|
14422
|
+
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey) {
|
|
14085
14423
|
return Promise.reject(new TerminalRequestDeferredError());
|
|
14086
14424
|
}
|
|
14087
14425
|
const controller = new AbortController();
|
|
14088
14426
|
const request = (async () => {
|
|
14089
14427
|
const bootstrap = await this.terminalPendingLaunchBootstrap(controller.signal);
|
|
14428
|
+
const boundPendingLaunch = bindPendingLaunchIntent(pendingLaunch, bootstrap);
|
|
14429
|
+
if (!boundPendingLaunch ||
|
|
14430
|
+
!this.markStagedPendingLaunchInFlight(boundPendingLaunch)) {
|
|
14431
|
+
throw new TerminalRequestDeferredError();
|
|
14432
|
+
}
|
|
14433
|
+
this.terminalPendingLaunch = {
|
|
14434
|
+
...boundPendingLaunch,
|
|
14435
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14436
|
+
};
|
|
14090
14437
|
const replay = await this.replayReadiness.wait();
|
|
14091
14438
|
if (this.destroyed ||
|
|
14092
14439
|
this.ended ||
|
|
@@ -14095,28 +14442,34 @@ class AiLiveAssist {
|
|
|
14095
14442
|
throw new TerminalRequestDeferredError();
|
|
14096
14443
|
}
|
|
14097
14444
|
this.replayReadiness.assertCurrent(replay);
|
|
14098
|
-
|
|
14445
|
+
const result = await this.runtime.launch({
|
|
14099
14446
|
siteKey: this.siteKey,
|
|
14100
14447
|
configRevision: bootstrap.configRevision,
|
|
14101
14448
|
path: path(),
|
|
14102
14449
|
tabId: this.tabId,
|
|
14103
|
-
mode:
|
|
14450
|
+
mode: boundPendingLaunch.mode,
|
|
14104
14451
|
anonymousId: this.visitorId(),
|
|
14105
14452
|
replay,
|
|
14106
14453
|
disclosureChecksum: bootstrap.consent.disclosureChecksum,
|
|
14107
|
-
scopes:
|
|
14454
|
+
scopes: boundPendingLaunch.scopes,
|
|
14108
14455
|
locale: this.locale,
|
|
14109
|
-
microphone:
|
|
14456
|
+
microphone: boundPendingLaunch.mode === "voice",
|
|
14110
14457
|
reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)")
|
|
14111
14458
|
.matches,
|
|
14112
|
-
protocolVersion:
|
|
14113
|
-
experienceMode:
|
|
14459
|
+
protocolVersion: boundPendingLaunch.protocolVersion,
|
|
14460
|
+
experienceMode: boundPendingLaunch.experienceMode,
|
|
14114
14461
|
...(this.activeFallback
|
|
14115
14462
|
? { fallbackIntent: this.activeFallback }
|
|
14116
14463
|
: {}),
|
|
14117
14464
|
lowPower: this.lowPowerMode(),
|
|
14118
|
-
idempotencyKey:
|
|
14465
|
+
idempotencyKey: boundPendingLaunch.idempotencyKey,
|
|
14119
14466
|
}, controller.signal);
|
|
14467
|
+
if (result.schemaVersion !== boundPendingLaunch.protocolVersion ||
|
|
14468
|
+
result.protocolVersion !== boundPendingLaunch.protocolVersion ||
|
|
14469
|
+
result.experienceMode !== boundPendingLaunch.experienceMode) {
|
|
14470
|
+
throw new client_js_1.RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
14471
|
+
}
|
|
14472
|
+
return result;
|
|
14120
14473
|
})();
|
|
14121
14474
|
return this.waitForTerminalRequestDeadline(request, controller);
|
|
14122
14475
|
}
|
|
@@ -14280,8 +14633,7 @@ class AiLiveAssist {
|
|
|
14280
14633
|
const retainTerminalLaunch = Boolean(this.terminalSessionAuthorization &&
|
|
14281
14634
|
this.terminalPendingLaunch &&
|
|
14282
14635
|
staged?.state === "in_flight" &&
|
|
14283
|
-
staged.pendingLaunch.
|
|
14284
|
-
this.terminalPendingLaunch.idempotencyKey);
|
|
14636
|
+
this.pendingLaunchMatches(staged.pendingLaunch, this.terminalPendingLaunch));
|
|
14285
14637
|
if (!retainTerminalLaunch)
|
|
14286
14638
|
this.clearOwnedStagedPendingLaunch();
|
|
14287
14639
|
this.lock?.cancelPendingHandoff?.();
|
|
@@ -14705,15 +15057,18 @@ class AiLiveAssist {
|
|
|
14705
15057
|
focusedHandoffPayload() {
|
|
14706
15058
|
this.transcript ?? (this.transcript = []);
|
|
14707
15059
|
const resume = this.readResume();
|
|
15060
|
+
const pendingLaunch = !resume && this.lastLaunch && this.bootstrap
|
|
15061
|
+
? bindPendingLaunchIntent(this.lastLaunch, this.bootstrap)
|
|
15062
|
+
: null;
|
|
14708
15063
|
return {
|
|
14709
|
-
v: 1,
|
|
15064
|
+
v: pendingLaunch ? 2 : 1,
|
|
14710
15065
|
resume,
|
|
14711
15066
|
transcript: this.transcript.map((entry) => ({ ...entry })),
|
|
14712
|
-
...(
|
|
15067
|
+
...(pendingLaunch
|
|
14713
15068
|
? {
|
|
14714
15069
|
pendingLaunch: {
|
|
14715
|
-
...
|
|
14716
|
-
scopes: [...
|
|
15070
|
+
...pendingLaunch,
|
|
15071
|
+
scopes: [...pendingLaunch.scopes],
|
|
14717
15072
|
},
|
|
14718
15073
|
}
|
|
14719
15074
|
: {}),
|
|
@@ -14728,33 +15083,157 @@ class AiLiveAssist {
|
|
|
14728
15083
|
}
|
|
14729
15084
|
}
|
|
14730
15085
|
readStagedPendingLaunch() {
|
|
14731
|
-
let raw = null;
|
|
14732
15086
|
try {
|
|
14733
|
-
|
|
14734
|
-
const
|
|
14735
|
-
|
|
15087
|
+
const v2Raw = localStorage.getItem(this.stagedPendingLaunchStorageKey);
|
|
15088
|
+
const parsedV2 = parseStagedPendingLaunch(v2Raw);
|
|
15089
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15090
|
+
if (v2Raw && !v2) {
|
|
14736
15091
|
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14737
15092
|
}
|
|
14738
|
-
|
|
15093
|
+
const legacyRaw = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15094
|
+
const parsedLegacy = parseStagedPendingLaunch(legacyRaw);
|
|
15095
|
+
const legacy = parsedLegacy?.v === 1 ? parsedLegacy : null;
|
|
15096
|
+
if (legacyRaw && !legacy) {
|
|
15097
|
+
localStorage.removeItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15098
|
+
}
|
|
15099
|
+
if (!v2)
|
|
15100
|
+
return legacy;
|
|
15101
|
+
if (!legacy)
|
|
15102
|
+
return v2;
|
|
15103
|
+
const authorityOrder = compareTransferAuthority(v2.authority, legacy.authority);
|
|
15104
|
+
if (authorityOrder !== 0)
|
|
15105
|
+
return authorityOrder > 0 ? v2 : legacy;
|
|
15106
|
+
if (v2.sourceOwnerId !== legacy.sourceOwnerId ||
|
|
15107
|
+
!this.pendingLaunchBaseMatches(v2.pendingLaunch, legacy.pendingLaunch)) {
|
|
15108
|
+
return null;
|
|
15109
|
+
}
|
|
15110
|
+
// A V2 record is the only copy that preserves the exact launch body.
|
|
15111
|
+
// Prefer it for an equal authority; the legacy copy is cleaned only
|
|
15112
|
+
// after a matching V2 write has been verified.
|
|
15113
|
+
return v2;
|
|
14739
15114
|
}
|
|
14740
15115
|
catch {
|
|
14741
15116
|
return null;
|
|
14742
15117
|
}
|
|
14743
15118
|
}
|
|
14744
15119
|
persistStagedPendingLaunch(staged) {
|
|
15120
|
+
let rollback = null;
|
|
14745
15121
|
try {
|
|
14746
|
-
|
|
14747
|
-
|
|
15122
|
+
const serializedPendingLaunch = staged.v === 1
|
|
15123
|
+
? {
|
|
15124
|
+
mode: staged.pendingLaunch.mode,
|
|
15125
|
+
scopes: [...staged.pendingLaunch.scopes],
|
|
15126
|
+
idempotencyKey: staged.pendingLaunch.idempotencyKey,
|
|
15127
|
+
}
|
|
15128
|
+
: staged.pendingLaunch;
|
|
15129
|
+
const storageKey = staged.v === 2
|
|
15130
|
+
? this.stagedPendingLaunchStorageKey
|
|
15131
|
+
: this.legacyStagedPendingLaunchStorageKey;
|
|
15132
|
+
const serialized = JSON.stringify({
|
|
15133
|
+
...staged,
|
|
15134
|
+
pendingLaunch: serializedPendingLaunch,
|
|
15135
|
+
});
|
|
15136
|
+
const previousSerialized = localStorage.getItem(storageKey);
|
|
15137
|
+
const previousLegacySerialized = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15138
|
+
let removedMatchingLegacy = false;
|
|
15139
|
+
rollback = () => {
|
|
15140
|
+
if (localStorage.getItem(storageKey) === serialized) {
|
|
15141
|
+
if (previousSerialized === null)
|
|
15142
|
+
localStorage.removeItem(storageKey);
|
|
15143
|
+
else
|
|
15144
|
+
localStorage.setItem(storageKey, previousSerialized);
|
|
15145
|
+
}
|
|
15146
|
+
if (removedMatchingLegacy &&
|
|
15147
|
+
previousLegacySerialized !== null &&
|
|
15148
|
+
localStorage.getItem(this.legacyStagedPendingLaunchStorageKey) ===
|
|
15149
|
+
null) {
|
|
15150
|
+
localStorage.setItem(this.legacyStagedPendingLaunchStorageKey, previousLegacySerialized);
|
|
15151
|
+
}
|
|
15152
|
+
};
|
|
15153
|
+
localStorage.setItem(storageKey, serialized);
|
|
15154
|
+
const normalizedPendingLaunch = staged.v === 1
|
|
15155
|
+
? {
|
|
15156
|
+
...serializedPendingLaunch,
|
|
15157
|
+
protocolVersion: null,
|
|
15158
|
+
experienceMode: null,
|
|
15159
|
+
}
|
|
15160
|
+
: staged.pendingLaunch;
|
|
15161
|
+
const expected = {
|
|
15162
|
+
...staged,
|
|
15163
|
+
pendingLaunch: normalizedPendingLaunch,
|
|
15164
|
+
};
|
|
15165
|
+
const directReadback = parseStagedPendingLaunch(localStorage.getItem(storageKey));
|
|
15166
|
+
if (canonicalJson(directReadback) !== canonicalJson(expected)) {
|
|
15167
|
+
rollback();
|
|
15168
|
+
return false;
|
|
15169
|
+
}
|
|
15170
|
+
if (staged.v === 2) {
|
|
15171
|
+
const legacySerialized = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15172
|
+
const legacy = parseStagedPendingLaunch(legacySerialized);
|
|
15173
|
+
const equalAuthority = legacy?.v === 1 &&
|
|
15174
|
+
compareTransferAuthority(legacy.authority, staged.authority) === 0;
|
|
15175
|
+
if (equalAuthority &&
|
|
15176
|
+
(legacy.sourceOwnerId !== staged.sourceOwnerId ||
|
|
15177
|
+
!this.pendingLaunchBaseMatches(legacy.pendingLaunch, staged.pendingLaunch))) {
|
|
15178
|
+
rollback();
|
|
15179
|
+
return false;
|
|
15180
|
+
}
|
|
15181
|
+
if (legacy?.v === 1 &&
|
|
15182
|
+
legacy.sourceOwnerId === staged.sourceOwnerId &&
|
|
15183
|
+
equalAuthority &&
|
|
15184
|
+
this.pendingLaunchBaseMatches(legacy.pendingLaunch, staged.pendingLaunch) &&
|
|
15185
|
+
legacySerialized !== null &&
|
|
15186
|
+
localStorage.getItem(this.legacyStagedPendingLaunchStorageKey) ===
|
|
15187
|
+
legacySerialized) {
|
|
15188
|
+
localStorage.removeItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15189
|
+
removedMatchingLegacy = true;
|
|
15190
|
+
}
|
|
15191
|
+
}
|
|
15192
|
+
const persisted = canonicalJson(this.readStagedPendingLaunch()) ===
|
|
15193
|
+
canonicalJson(expected);
|
|
15194
|
+
if (!persisted)
|
|
15195
|
+
rollback();
|
|
15196
|
+
return persisted;
|
|
14748
15197
|
}
|
|
14749
15198
|
catch {
|
|
15199
|
+
try {
|
|
15200
|
+
rollback?.();
|
|
15201
|
+
}
|
|
15202
|
+
catch {
|
|
15203
|
+
// Storage is already unavailable. Leave the launch unclaimed and
|
|
15204
|
+
// fail closed rather than trusting a partially observed write.
|
|
15205
|
+
}
|
|
14750
15206
|
return false;
|
|
14751
15207
|
}
|
|
14752
15208
|
}
|
|
15209
|
+
pendingLaunchBaseMatches(left, right) {
|
|
15210
|
+
return (canonicalJson({
|
|
15211
|
+
mode: left.mode,
|
|
15212
|
+
scopes: left.scopes,
|
|
15213
|
+
idempotencyKey: left.idempotencyKey,
|
|
15214
|
+
}) ===
|
|
15215
|
+
canonicalJson({
|
|
15216
|
+
mode: right.mode,
|
|
15217
|
+
scopes: right.scopes,
|
|
15218
|
+
idempotencyKey: right.idempotencyKey,
|
|
15219
|
+
}));
|
|
15220
|
+
}
|
|
14753
15221
|
pendingLaunchMatches(left, right) {
|
|
14754
|
-
|
|
15222
|
+
const leftBound = pendingLaunchIsBound(left);
|
|
15223
|
+
const rightBound = pendingLaunchIsBound(right);
|
|
15224
|
+
if (leftBound !== rightBound)
|
|
15225
|
+
return false;
|
|
15226
|
+
if (leftBound && rightBound) {
|
|
15227
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
15228
|
+
}
|
|
15229
|
+
return this.pendingLaunchBaseMatches(left, right);
|
|
14755
15230
|
}
|
|
14756
15231
|
stagePendingLaunch(pendingLaunch, authority, sourceOwnerId) {
|
|
14757
|
-
|
|
15232
|
+
const boundPendingLaunch = this.bootstrap
|
|
15233
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15234
|
+
: null;
|
|
15235
|
+
if (!boundPendingLaunch ||
|
|
15236
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(sourceOwnerId) ||
|
|
14758
15237
|
sourceOwnerId === authority.ownerId) {
|
|
14759
15238
|
return false;
|
|
14760
15239
|
}
|
|
@@ -14765,35 +15244,82 @@ class AiLiveAssist {
|
|
|
14765
15244
|
if (authorityOrder < 0 ||
|
|
14766
15245
|
(authorityOrder === 0 &&
|
|
14767
15246
|
(existing?.sourceOwnerId !== sourceOwnerId ||
|
|
14768
|
-
!this.pendingLaunchMatches(existing.pendingLaunch
|
|
15247
|
+
!this.pendingLaunchMatches(!pendingLaunchIsBound(existing.pendingLaunch) && this.bootstrap
|
|
15248
|
+
? (bindPendingLaunchIntent(existing.pendingLaunch, this.bootstrap) ?? existing.pendingLaunch)
|
|
15249
|
+
: existing.pendingLaunch, boundPendingLaunch) ||
|
|
14769
15250
|
existing.state !== "prepared"))) {
|
|
14770
15251
|
return false;
|
|
14771
15252
|
}
|
|
14772
15253
|
return this.persistStagedPendingLaunch({
|
|
14773
|
-
v:
|
|
15254
|
+
v: 2,
|
|
14774
15255
|
sourceOwnerId,
|
|
14775
15256
|
authority: { ...authority },
|
|
14776
15257
|
state: "prepared",
|
|
14777
15258
|
claimAuthority: null,
|
|
14778
15259
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14779
15260
|
pendingLaunch: {
|
|
14780
|
-
...
|
|
14781
|
-
scopes: [...
|
|
15261
|
+
...boundPendingLaunch,
|
|
15262
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14782
15263
|
},
|
|
14783
15264
|
});
|
|
14784
15265
|
}
|
|
14785
15266
|
clearStagedPendingLaunch(pendingLaunch, authority) {
|
|
14786
15267
|
const staged = this.readStagedPendingLaunch();
|
|
15268
|
+
const comparablePending = this.bootstrap && !pendingLaunchIsBound(pendingLaunch)
|
|
15269
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15270
|
+
: pendingLaunch;
|
|
15271
|
+
const comparableStaged = this.bootstrap && staged && !pendingLaunchIsBound(staged.pendingLaunch)
|
|
15272
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15273
|
+
: staged?.pendingLaunch;
|
|
14787
15274
|
if (!staged ||
|
|
15275
|
+
!comparablePending ||
|
|
15276
|
+
!comparableStaged ||
|
|
14788
15277
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14789
|
-
!this.pendingLaunchMatches(
|
|
14790
|
-
return;
|
|
15278
|
+
!this.pendingLaunchMatches(comparableStaged, comparablePending)) {
|
|
15279
|
+
return false;
|
|
14791
15280
|
}
|
|
14792
15281
|
try {
|
|
14793
|
-
|
|
15282
|
+
const snapshots = [
|
|
15283
|
+
{
|
|
15284
|
+
key: this.stagedPendingLaunchStorageKey,
|
|
15285
|
+
raw: localStorage.getItem(this.stagedPendingLaunchStorageKey),
|
|
15286
|
+
version: 2,
|
|
15287
|
+
},
|
|
15288
|
+
{
|
|
15289
|
+
key: this.legacyStagedPendingLaunchStorageKey,
|
|
15290
|
+
raw: localStorage.getItem(this.legacyStagedPendingLaunchStorageKey),
|
|
15291
|
+
version: 1,
|
|
15292
|
+
},
|
|
15293
|
+
];
|
|
15294
|
+
const removable = [];
|
|
15295
|
+
for (const snapshot of snapshots) {
|
|
15296
|
+
if (snapshot.raw === null)
|
|
15297
|
+
continue;
|
|
15298
|
+
const shadow = parseStagedPendingLaunch(snapshot.raw);
|
|
15299
|
+
if (!shadow || shadow.v !== snapshot.version)
|
|
15300
|
+
return false;
|
|
15301
|
+
const authorityOrder = compareTransferAuthority(shadow.authority, staged.authority);
|
|
15302
|
+
if (authorityOrder > 0)
|
|
15303
|
+
continue;
|
|
15304
|
+
if (authorityOrder === 0 &&
|
|
15305
|
+
(shadow.sourceOwnerId !== staged.sourceOwnerId ||
|
|
15306
|
+
!this.pendingLaunchBaseMatches(shadow.pendingLaunch, staged.pendingLaunch))) {
|
|
15307
|
+
return false;
|
|
15308
|
+
}
|
|
15309
|
+
removable.push(snapshot);
|
|
15310
|
+
}
|
|
15311
|
+
if (removable.some((snapshot) => localStorage.getItem(snapshot.key) !== snapshot.raw)) {
|
|
15312
|
+
return false;
|
|
15313
|
+
}
|
|
15314
|
+
for (const snapshot of removable) {
|
|
15315
|
+
if (localStorage.getItem(snapshot.key) !== snapshot.raw)
|
|
15316
|
+
return false;
|
|
15317
|
+
localStorage.removeItem(snapshot.key);
|
|
15318
|
+
}
|
|
15319
|
+
return removable.every((snapshot) => localStorage.getItem(snapshot.key) === null);
|
|
14794
15320
|
}
|
|
14795
15321
|
catch {
|
|
14796
|
-
|
|
15322
|
+
return false;
|
|
14797
15323
|
}
|
|
14798
15324
|
}
|
|
14799
15325
|
clearOwnedStagedPendingLaunch() {
|
|
@@ -14810,7 +15336,7 @@ class AiLiveAssist {
|
|
|
14810
15336
|
const staged = this.readStagedPendingLaunch();
|
|
14811
15337
|
if (!pendingLaunch ||
|
|
14812
15338
|
!staged ||
|
|
14813
|
-
staged.pendingLaunch
|
|
15339
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch) ||
|
|
14814
15340
|
this.terminalEndIntent?.launchIdempotencyKey !==
|
|
14815
15341
|
pendingLaunch.idempotencyKey) {
|
|
14816
15342
|
return;
|
|
@@ -14821,11 +15347,15 @@ class AiLiveAssist {
|
|
|
14821
15347
|
const pendingLaunch = parseFocusedHandoffPayload(value)?.pendingLaunch;
|
|
14822
15348
|
if (!pendingLaunch)
|
|
14823
15349
|
return true;
|
|
15350
|
+
const boundPendingLaunch = this.bootstrap
|
|
15351
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15352
|
+
: null;
|
|
14824
15353
|
const staged = this.readStagedPendingLaunch();
|
|
14825
|
-
if (!
|
|
15354
|
+
if (!boundPendingLaunch ||
|
|
15355
|
+
!staged ||
|
|
14826
15356
|
staged.state !== "prepared" ||
|
|
14827
15357
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14828
|
-
!this.pendingLaunchMatches(staged.pendingLaunch,
|
|
15358
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, boundPendingLaunch)) {
|
|
14829
15359
|
return false;
|
|
14830
15360
|
}
|
|
14831
15361
|
return this.persistStagedPendingLaunch({
|
|
@@ -14869,6 +15399,12 @@ class AiLiveAssist {
|
|
|
14869
15399
|
}
|
|
14870
15400
|
markStagedPendingLaunchInFlight(pendingLaunch) {
|
|
14871
15401
|
const staged = this.readStagedPendingLaunch();
|
|
15402
|
+
const boundPendingLaunch = pendingLaunchIsBound(pendingLaunch)
|
|
15403
|
+
? pendingLaunch
|
|
15404
|
+
: this.bootstrap
|
|
15405
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15406
|
+
: null;
|
|
15407
|
+
const durablePendingLaunch = boundPendingLaunch ?? pendingLaunch;
|
|
14872
15408
|
// AiLiveAssist always installs a TabActivityLock with durable authority.
|
|
14873
15409
|
// Keep prototype-level test harnesses that intentionally omit the lock
|
|
14874
15410
|
// implementation isolated from the persistence contract.
|
|
@@ -14880,40 +15416,54 @@ class AiLiveAssist {
|
|
|
14880
15416
|
}
|
|
14881
15417
|
if (!staged) {
|
|
14882
15418
|
return this.persistStagedPendingLaunch({
|
|
14883
|
-
v: 1,
|
|
15419
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14884
15420
|
sourceOwnerId: randomId("launch_source"),
|
|
14885
15421
|
authority: { ...ownedAuthority },
|
|
14886
15422
|
state: "in_flight",
|
|
14887
15423
|
claimAuthority: { ...ownedAuthority },
|
|
14888
15424
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14889
15425
|
pendingLaunch: {
|
|
14890
|
-
...
|
|
14891
|
-
scopes: [...
|
|
15426
|
+
...durablePendingLaunch,
|
|
15427
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14892
15428
|
},
|
|
14893
15429
|
});
|
|
14894
15430
|
}
|
|
15431
|
+
const comparableStagedPendingLaunch = pendingLaunchIsBound(staged.pendingLaunch)
|
|
15432
|
+
? staged.pendingLaunch
|
|
15433
|
+
: this.bootstrap
|
|
15434
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15435
|
+
: null;
|
|
14895
15436
|
if (staged.pendingLaunch.idempotencyKey !== pendingLaunch.idempotencyKey ||
|
|
14896
15437
|
!staged.claimAuthority ||
|
|
14897
15438
|
compareTransferAuthority(staged.claimAuthority, ownedAuthority) !== 0 ||
|
|
14898
|
-
(staged.state !== "claimed" && staged.state !== "in_flight")
|
|
15439
|
+
(staged.state !== "claimed" && staged.state !== "in_flight") ||
|
|
15440
|
+
(boundPendingLaunch
|
|
15441
|
+
? !comparableStagedPendingLaunch ||
|
|
15442
|
+
!this.pendingLaunchMatches(comparableStagedPendingLaunch, boundPendingLaunch)
|
|
15443
|
+
: !this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch))) {
|
|
14899
15444
|
return false;
|
|
14900
15445
|
}
|
|
14901
|
-
if (staged.state === "in_flight"
|
|
15446
|
+
if (staged.state === "in_flight" &&
|
|
15447
|
+
staged.v === (boundPendingLaunch ? 2 : 1) &&
|
|
15448
|
+
this.pendingLaunchMatches(staged.pendingLaunch, durablePendingLaunch)) {
|
|
14902
15449
|
return true;
|
|
15450
|
+
}
|
|
14903
15451
|
return this.persistStagedPendingLaunch({
|
|
14904
15452
|
...staged,
|
|
15453
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14905
15454
|
state: "in_flight",
|
|
14906
15455
|
pendingLaunch: {
|
|
14907
|
-
...
|
|
14908
|
-
scopes: [...
|
|
15456
|
+
...durablePendingLaunch,
|
|
15457
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14909
15458
|
},
|
|
14910
15459
|
});
|
|
14911
15460
|
}
|
|
14912
15461
|
acceptStagedPendingLaunch(pendingLaunch, result) {
|
|
14913
15462
|
const staged = this.readStagedPendingLaunch();
|
|
14914
|
-
if (!staged
|
|
14915
|
-
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15463
|
+
if (!staged)
|
|
14916
15464
|
return true;
|
|
15465
|
+
if (!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15466
|
+
return false;
|
|
14917
15467
|
}
|
|
14918
15468
|
const ownedAuthority = this.lock.ownedAuthority?.() ?? null;
|
|
14919
15469
|
const stored = this.readResume();
|
|
@@ -14937,17 +15487,11 @@ class AiLiveAssist {
|
|
|
14937
15487
|
})) {
|
|
14938
15488
|
return false;
|
|
14939
15489
|
}
|
|
14940
|
-
|
|
14941
|
-
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14942
|
-
return localStorage.getItem(this.stagedPendingLaunchStorageKey) === null;
|
|
14943
|
-
}
|
|
14944
|
-
catch {
|
|
14945
|
-
return false;
|
|
14946
|
-
}
|
|
15490
|
+
return this.clearStagedPendingLaunch(staged.pendingLaunch, staged.authority);
|
|
14947
15491
|
}
|
|
14948
15492
|
hasStagedPendingLaunch(pendingLaunch) {
|
|
14949
|
-
|
|
14950
|
-
|
|
15493
|
+
const staged = this.readStagedPendingLaunch();
|
|
15494
|
+
return Boolean(staged && this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch));
|
|
14951
15495
|
}
|
|
14952
15496
|
storeTransferAuthority(authority) {
|
|
14953
15497
|
const current = this.readTransferAuthority();
|
|
@@ -15025,12 +15569,21 @@ class AiLiveAssist {
|
|
|
15025
15569
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15026
15570
|
return null;
|
|
15027
15571
|
try {
|
|
15028
|
-
const
|
|
15029
|
-
const
|
|
15030
|
-
|
|
15572
|
+
const v2Serialized = sessionStorage.getItem(this.freshAiReturnProvisionalStorageKey);
|
|
15573
|
+
const parsedV2 = parseFreshAiReturnProvisional(v2Serialized ? JSON.parse(v2Serialized) : null);
|
|
15574
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15575
|
+
if (v2Serialized && !v2) {
|
|
15031
15576
|
sessionStorage.removeItem(this.freshAiReturnProvisionalStorageKey);
|
|
15032
15577
|
}
|
|
15033
|
-
|
|
15578
|
+
if (v2)
|
|
15579
|
+
return v2;
|
|
15580
|
+
const legacySerialized = sessionStorage.getItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15581
|
+
const parsedLegacy = parseFreshAiReturnProvisional(legacySerialized ? JSON.parse(legacySerialized) : null);
|
|
15582
|
+
const legacy = parsedLegacy?.v === 1 ? parsedLegacy : null;
|
|
15583
|
+
if (legacySerialized && !legacy) {
|
|
15584
|
+
sessionStorage.removeItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15585
|
+
}
|
|
15586
|
+
return legacy;
|
|
15034
15587
|
}
|
|
15035
15588
|
catch {
|
|
15036
15589
|
return null;
|
|
@@ -15040,9 +15593,54 @@ class AiLiveAssist {
|
|
|
15040
15593
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15041
15594
|
return false;
|
|
15042
15595
|
try {
|
|
15043
|
-
|
|
15596
|
+
const serialized = provisional.v === 1
|
|
15597
|
+
? {
|
|
15598
|
+
v: 1,
|
|
15599
|
+
linkId: provisional.linkId,
|
|
15600
|
+
conversationDisplayId: provisional.conversationDisplayId,
|
|
15601
|
+
mode: provisional.mode,
|
|
15602
|
+
phase: provisional.phase,
|
|
15603
|
+
launchIdempotencyKey: provisional.launchIdempotencyKey,
|
|
15604
|
+
sessionId: provisional.sessionId,
|
|
15605
|
+
expiresAt: provisional.expiresAt,
|
|
15606
|
+
}
|
|
15607
|
+
: provisional;
|
|
15608
|
+
const storageKey = provisional.v === 2
|
|
15609
|
+
? this.freshAiReturnProvisionalStorageKey
|
|
15610
|
+
: this.legacyFreshAiReturnProvisionalStorageKey;
|
|
15611
|
+
sessionStorage.setItem(storageKey, JSON.stringify(serialized));
|
|
15612
|
+
const normalized = provisional.v === 1
|
|
15613
|
+
? {
|
|
15614
|
+
...provisional,
|
|
15615
|
+
protocolVersion: null,
|
|
15616
|
+
experienceMode: null,
|
|
15617
|
+
scopes: null,
|
|
15618
|
+
}
|
|
15619
|
+
: provisional;
|
|
15620
|
+
const directReadback = parseFreshAiReturnProvisional(JSON.parse(sessionStorage.getItem(storageKey) ?? "null"));
|
|
15621
|
+
if (canonicalJson(directReadback) !== canonicalJson(normalized)) {
|
|
15622
|
+
return false;
|
|
15623
|
+
}
|
|
15624
|
+
if (provisional.v === 2) {
|
|
15625
|
+
const legacySerialized = sessionStorage.getItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15626
|
+
const legacy = parseFreshAiReturnProvisional(legacySerialized ? JSON.parse(legacySerialized) : null);
|
|
15627
|
+
const sameReturnLineage = legacy?.v === 1 &&
|
|
15628
|
+
legacy.linkId === provisional.linkId &&
|
|
15629
|
+
legacy.conversationDisplayId === provisional.conversationDisplayId &&
|
|
15630
|
+
legacy.mode === provisional.mode &&
|
|
15631
|
+
legacy.expiresAt === provisional.expiresAt;
|
|
15632
|
+
if (sameReturnLineage &&
|
|
15633
|
+
(legacy.phase === "released" ||
|
|
15634
|
+
provisional.phase === "released" ||
|
|
15635
|
+
(legacy.phase === provisional.phase &&
|
|
15636
|
+
legacy.launchIdempotencyKey ===
|
|
15637
|
+
provisional.launchIdempotencyKey &&
|
|
15638
|
+
legacy.sessionId === provisional.sessionId))) {
|
|
15639
|
+
sessionStorage.removeItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15640
|
+
}
|
|
15641
|
+
}
|
|
15044
15642
|
return (canonicalJson(this.readFreshAiReturnProvisional()) ===
|
|
15045
|
-
canonicalJson(
|
|
15643
|
+
canonicalJson(normalized));
|
|
15046
15644
|
}
|
|
15047
15645
|
catch {
|
|
15048
15646
|
return false;
|
|
@@ -15052,19 +15650,21 @@ class AiLiveAssist {
|
|
|
15052
15650
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15053
15651
|
return;
|
|
15054
15652
|
try {
|
|
15055
|
-
const
|
|
15653
|
+
const storageKey = expected?.v === 1
|
|
15654
|
+
? this.legacyFreshAiReturnProvisionalStorageKey
|
|
15655
|
+
: this.freshAiReturnProvisionalStorageKey;
|
|
15656
|
+
const serialized = sessionStorage.getItem(storageKey);
|
|
15056
15657
|
if (!serialized)
|
|
15057
15658
|
return;
|
|
15058
15659
|
if (expected) {
|
|
15059
15660
|
const stored = parseFreshAiReturnProvisional(JSON.parse(serialized));
|
|
15060
15661
|
if (canonicalJson(stored) !== canonicalJson(expected))
|
|
15061
15662
|
return;
|
|
15062
|
-
if (sessionStorage.getItem(
|
|
15063
|
-
serialized) {
|
|
15663
|
+
if (sessionStorage.getItem(storageKey) !== serialized) {
|
|
15064
15664
|
return;
|
|
15065
15665
|
}
|
|
15066
15666
|
}
|
|
15067
|
-
sessionStorage.removeItem(
|
|
15667
|
+
sessionStorage.removeItem(storageKey);
|
|
15068
15668
|
}
|
|
15069
15669
|
catch {
|
|
15070
15670
|
/* noop */
|
|
@@ -15655,10 +16255,7 @@ class AiLiveAssist {
|
|
|
15655
16255
|
!this.view ||
|
|
15656
16256
|
(record &&
|
|
15657
16257
|
(!record.tabId ||
|
|
15658
|
-
record.
|
|
15659
|
-
(0, exports.resolveAiGuideProtocolVersion)(this.bootstrap) ||
|
|
15660
|
-
record.experienceMode !==
|
|
15661
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16258
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15662
16259
|
(pendingLaunch &&
|
|
15663
16260
|
(this.visitorActiveTakeoverOnly ||
|
|
15664
16261
|
((this.bootstrap.experienceMode ?? "standard") === "voice_presence" &&
|
|
@@ -15694,10 +16291,11 @@ class AiLiveAssist {
|
|
|
15694
16291
|
this.lock.release();
|
|
15695
16292
|
};
|
|
15696
16293
|
const preparedMatches = record
|
|
15697
|
-
? prepared?.resume
|
|
16294
|
+
? Boolean(prepared?.resume &&
|
|
16295
|
+
this.sameResumeAuthorityIdentity(prepared.resume, record))
|
|
15698
16296
|
: Boolean(pendingLaunch &&
|
|
15699
|
-
prepared?.pendingLaunch
|
|
15700
|
-
|
|
16297
|
+
prepared?.pendingLaunch &&
|
|
16298
|
+
this.pendingLaunchMatches(prepared.pendingLaunch, pendingLaunch));
|
|
15701
16299
|
if (!payload ||
|
|
15702
16300
|
(!record && !pendingLaunch) ||
|
|
15703
16301
|
!this.bootstrap ||
|
|
@@ -15710,10 +16308,7 @@ class AiLiveAssist {
|
|
|
15710
16308
|
!preparedMatches ||
|
|
15711
16309
|
(record &&
|
|
15712
16310
|
(!record.tabId ||
|
|
15713
|
-
record.
|
|
15714
|
-
(0, exports.resolveAiGuideProtocolVersion)(this.bootstrap) ||
|
|
15715
|
-
record.experienceMode !==
|
|
15716
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16311
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15717
16312
|
(pendingLaunch && this.visitorActiveTakeoverOnly)) {
|
|
15718
16313
|
abandonUnusableCommit();
|
|
15719
16314
|
return;
|
|
@@ -15725,7 +16320,7 @@ class AiLiveAssist {
|
|
|
15725
16320
|
return;
|
|
15726
16321
|
}
|
|
15727
16322
|
if (this.lastLaunch &&
|
|
15728
|
-
this.lastLaunch
|
|
16323
|
+
!this.pendingLaunchMatches(this.lastLaunch, stagedPending)) {
|
|
15729
16324
|
this.clearStagedPendingLaunch(stagedPending, authority);
|
|
15730
16325
|
}
|
|
15731
16326
|
else {
|
|
@@ -15737,6 +16332,12 @@ class AiLiveAssist {
|
|
|
15737
16332
|
return;
|
|
15738
16333
|
}
|
|
15739
16334
|
const storedResume = record ? this.readResume() : null;
|
|
16335
|
+
if (record &&
|
|
16336
|
+
storedResume?.sessionId === record.sessionId &&
|
|
16337
|
+
!this.sameResumeAuthorityIdentity(record, storedResume)) {
|
|
16338
|
+
abandonUnusableCommit();
|
|
16339
|
+
return;
|
|
16340
|
+
}
|
|
15740
16341
|
const effectivePayload = record &&
|
|
15741
16342
|
storedResume?.sessionId === record.sessionId &&
|
|
15742
16343
|
storedResume.resumeCredential !== record.resumeCredential
|
|
@@ -15777,11 +16378,11 @@ class AiLiveAssist {
|
|
|
15777
16378
|
completeFocusedTabRelinquish(value) {
|
|
15778
16379
|
const payload = parseFocusedHandoffPayload(value);
|
|
15779
16380
|
const sameResume = Boolean(payload?.resume &&
|
|
15780
|
-
this.focusedHandoffInFlight?.resume
|
|
15781
|
-
|
|
16381
|
+
this.focusedHandoffInFlight?.resume &&
|
|
16382
|
+
this.sameResumeAuthorityIdentity(this.focusedHandoffInFlight.resume, payload.resume));
|
|
15782
16383
|
const samePendingLaunch = Boolean(payload?.pendingLaunch &&
|
|
15783
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15784
|
-
|
|
16384
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16385
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, payload.pendingLaunch));
|
|
15785
16386
|
if (!payload || (!sameResume && !samePendingLaunch)) {
|
|
15786
16387
|
return;
|
|
15787
16388
|
}
|
|
@@ -15812,8 +16413,8 @@ class AiLiveAssist {
|
|
|
15812
16413
|
this.clearStagedPendingLaunch(pendingLaunch, authority);
|
|
15813
16414
|
}
|
|
15814
16415
|
if (pendingLaunch &&
|
|
15815
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15816
|
-
|
|
16416
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16417
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, pendingLaunch)) {
|
|
15817
16418
|
this.focusedHandoffInFlight = null;
|
|
15818
16419
|
if (this.terminalRequestPending ||
|
|
15819
16420
|
this.ended ||
|
|
@@ -15835,7 +16436,7 @@ class AiLiveAssist {
|
|
|
15835
16436
|
queueMicrotask(() => {
|
|
15836
16437
|
const retry = this.lastLaunch;
|
|
15837
16438
|
if (!retry ||
|
|
15838
|
-
retry
|
|
16439
|
+
!this.pendingLaunchMatches(retry, pendingLaunch) ||
|
|
15839
16440
|
this.terminalRequestPending ||
|
|
15840
16441
|
this.ended ||
|
|
15841
16442
|
this.destroyed ||
|
|
@@ -15844,7 +16445,9 @@ class AiLiveAssist {
|
|
|
15844
16445
|
return;
|
|
15845
16446
|
}
|
|
15846
16447
|
this.lastLaunch = null;
|
|
15847
|
-
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey
|
|
16448
|
+
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey, {
|
|
16449
|
+
pendingLaunch: retry,
|
|
16450
|
+
});
|
|
15848
16451
|
});
|
|
15849
16452
|
return;
|
|
15850
16453
|
}
|
|
@@ -15910,7 +16513,7 @@ class AiLiveAssist {
|
|
|
15910
16513
|
this.view &&
|
|
15911
16514
|
!this.visitorActiveTakeoverOnly) {
|
|
15912
16515
|
this.lastLaunch = null;
|
|
15913
|
-
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey);
|
|
16516
|
+
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey, { pendingLaunch: deferredLaunch });
|
|
15914
16517
|
return;
|
|
15915
16518
|
}
|
|
15916
16519
|
this.lock.release();
|