@oxvo/ai-live-assist 7.4.37 → 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 +792 -193
- 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 +792 -193
- 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") ||
|
|
@@ -13105,7 +13395,7 @@ class AiLiveAssist {
|
|
|
13105
13395
|
return;
|
|
13106
13396
|
}
|
|
13107
13397
|
this.messengerReturnRetryRemaining -= 1;
|
|
13108
|
-
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 });
|
|
13109
13399
|
return retryAttempt.finally(() => {
|
|
13110
13400
|
if (this.result && !this.ended) {
|
|
13111
13401
|
this.messengerReturnRetryRemaining = null;
|
|
@@ -13125,7 +13415,7 @@ class AiLiveAssist {
|
|
|
13125
13415
|
return this.resume(resume);
|
|
13126
13416
|
}
|
|
13127
13417
|
if (this.lastLaunch) {
|
|
13128
|
-
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 });
|
|
13129
13419
|
}
|
|
13130
13420
|
}
|
|
13131
13421
|
scheduleReconnect(force = false) {
|
|
@@ -13455,6 +13745,18 @@ class AiLiveAssist {
|
|
|
13455
13745
|
canonicalJson(left.messengerBinding) ===
|
|
13456
13746
|
canonicalJson(right.messengerBinding));
|
|
13457
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
|
+
}
|
|
13458
13760
|
sameModeUpgradeIntent(left, right) {
|
|
13459
13761
|
return (left?.requestId === right?.requestId &&
|
|
13460
13762
|
left?.resumeIdempotencyKey === right?.resumeIdempotencyKey &&
|
|
@@ -13823,10 +14125,22 @@ class AiLiveAssist {
|
|
|
13823
14125
|
this.state("ended");
|
|
13824
14126
|
}
|
|
13825
14127
|
captureTerminalAuthorizedResult(result, sourceRecord = null) {
|
|
13826
|
-
|
|
13827
|
-
(
|
|
13828
|
-
|
|
13829
|
-
|
|
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))) {
|
|
13830
14144
|
return false;
|
|
13831
14145
|
}
|
|
13832
14146
|
const currentIntent = this.terminalEndIntent;
|
|
@@ -13852,6 +14166,22 @@ class AiLiveAssist {
|
|
|
13852
14166
|
}
|
|
13853
14167
|
return true;
|
|
13854
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
|
+
}
|
|
13855
14185
|
completeRuntimeEnd() {
|
|
13856
14186
|
if (this.ended)
|
|
13857
14187
|
return Promise.resolve();
|
|
@@ -13882,8 +14212,9 @@ class AiLiveAssist {
|
|
|
13882
14212
|
if (terminalAuthorization) {
|
|
13883
14213
|
return this.waitForTerminalRequestDeadline(terminalAuthorization, this.terminalSessionAuthorizationController).then((authorized) => {
|
|
13884
14214
|
const result = authorized ?? this.result ?? this.modeUpgradeTerminalResult;
|
|
13885
|
-
if (result
|
|
13886
|
-
this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)
|
|
14215
|
+
if (result &&
|
|
14216
|
+
!this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)) {
|
|
14217
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
13887
14218
|
}
|
|
13888
14219
|
return this.finishRuntimeEndAttempt(result);
|
|
13889
14220
|
}, (error) => {
|
|
@@ -13900,7 +14231,9 @@ class AiLiveAssist {
|
|
|
13900
14231
|
}
|
|
13901
14232
|
if (this.terminalPendingLaunch) {
|
|
13902
14233
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13903
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14234
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14235
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14236
|
+
}
|
|
13904
14237
|
return this.finishRuntimeEndAttempt(result);
|
|
13905
14238
|
}, (recoveryError) => this.retryTerminalEnd(recoveryError));
|
|
13906
14239
|
}
|
|
@@ -13927,7 +14260,9 @@ class AiLiveAssist {
|
|
|
13927
14260
|
return this.finishRuntimeEndAttempt(null);
|
|
13928
14261
|
if (this.terminalPendingLaunch) {
|
|
13929
14262
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13930
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14263
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14264
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14265
|
+
}
|
|
13931
14266
|
return this.finishRuntimeEndAttempt(result);
|
|
13932
14267
|
}, (error) => this.retryTerminalEnd(error));
|
|
13933
14268
|
}
|
|
@@ -14084,13 +14419,21 @@ class AiLiveAssist {
|
|
|
14084
14419
|
requestTerminalPendingLaunch(pendingLaunch) {
|
|
14085
14420
|
const intent = this.terminalEndIntent;
|
|
14086
14421
|
if (!intent ||
|
|
14087
|
-
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey
|
|
14088
|
-
!this.markStagedPendingLaunchInFlight(pendingLaunch)) {
|
|
14422
|
+
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey) {
|
|
14089
14423
|
return Promise.reject(new TerminalRequestDeferredError());
|
|
14090
14424
|
}
|
|
14091
14425
|
const controller = new AbortController();
|
|
14092
14426
|
const request = (async () => {
|
|
14093
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
|
+
};
|
|
14094
14437
|
const replay = await this.replayReadiness.wait();
|
|
14095
14438
|
if (this.destroyed ||
|
|
14096
14439
|
this.ended ||
|
|
@@ -14099,28 +14442,34 @@ class AiLiveAssist {
|
|
|
14099
14442
|
throw new TerminalRequestDeferredError();
|
|
14100
14443
|
}
|
|
14101
14444
|
this.replayReadiness.assertCurrent(replay);
|
|
14102
|
-
|
|
14445
|
+
const result = await this.runtime.launch({
|
|
14103
14446
|
siteKey: this.siteKey,
|
|
14104
14447
|
configRevision: bootstrap.configRevision,
|
|
14105
14448
|
path: path(),
|
|
14106
14449
|
tabId: this.tabId,
|
|
14107
|
-
mode:
|
|
14450
|
+
mode: boundPendingLaunch.mode,
|
|
14108
14451
|
anonymousId: this.visitorId(),
|
|
14109
14452
|
replay,
|
|
14110
14453
|
disclosureChecksum: bootstrap.consent.disclosureChecksum,
|
|
14111
|
-
scopes:
|
|
14454
|
+
scopes: boundPendingLaunch.scopes,
|
|
14112
14455
|
locale: this.locale,
|
|
14113
|
-
microphone:
|
|
14456
|
+
microphone: boundPendingLaunch.mode === "voice",
|
|
14114
14457
|
reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)")
|
|
14115
14458
|
.matches,
|
|
14116
|
-
protocolVersion:
|
|
14117
|
-
experienceMode:
|
|
14459
|
+
protocolVersion: boundPendingLaunch.protocolVersion,
|
|
14460
|
+
experienceMode: boundPendingLaunch.experienceMode,
|
|
14118
14461
|
...(this.activeFallback
|
|
14119
14462
|
? { fallbackIntent: this.activeFallback }
|
|
14120
14463
|
: {}),
|
|
14121
14464
|
lowPower: this.lowPowerMode(),
|
|
14122
|
-
idempotencyKey:
|
|
14465
|
+
idempotencyKey: boundPendingLaunch.idempotencyKey,
|
|
14123
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;
|
|
14124
14473
|
})();
|
|
14125
14474
|
return this.waitForTerminalRequestDeadline(request, controller);
|
|
14126
14475
|
}
|
|
@@ -14284,8 +14633,7 @@ class AiLiveAssist {
|
|
|
14284
14633
|
const retainTerminalLaunch = Boolean(this.terminalSessionAuthorization &&
|
|
14285
14634
|
this.terminalPendingLaunch &&
|
|
14286
14635
|
staged?.state === "in_flight" &&
|
|
14287
|
-
staged.pendingLaunch.
|
|
14288
|
-
this.terminalPendingLaunch.idempotencyKey);
|
|
14636
|
+
this.pendingLaunchMatches(staged.pendingLaunch, this.terminalPendingLaunch));
|
|
14289
14637
|
if (!retainTerminalLaunch)
|
|
14290
14638
|
this.clearOwnedStagedPendingLaunch();
|
|
14291
14639
|
this.lock?.cancelPendingHandoff?.();
|
|
@@ -14709,15 +15057,18 @@ class AiLiveAssist {
|
|
|
14709
15057
|
focusedHandoffPayload() {
|
|
14710
15058
|
this.transcript ?? (this.transcript = []);
|
|
14711
15059
|
const resume = this.readResume();
|
|
15060
|
+
const pendingLaunch = !resume && this.lastLaunch && this.bootstrap
|
|
15061
|
+
? bindPendingLaunchIntent(this.lastLaunch, this.bootstrap)
|
|
15062
|
+
: null;
|
|
14712
15063
|
return {
|
|
14713
|
-
v: 1,
|
|
15064
|
+
v: pendingLaunch ? 2 : 1,
|
|
14714
15065
|
resume,
|
|
14715
15066
|
transcript: this.transcript.map((entry) => ({ ...entry })),
|
|
14716
|
-
...(
|
|
15067
|
+
...(pendingLaunch
|
|
14717
15068
|
? {
|
|
14718
15069
|
pendingLaunch: {
|
|
14719
|
-
...
|
|
14720
|
-
scopes: [...
|
|
15070
|
+
...pendingLaunch,
|
|
15071
|
+
scopes: [...pendingLaunch.scopes],
|
|
14721
15072
|
},
|
|
14722
15073
|
}
|
|
14723
15074
|
: {}),
|
|
@@ -14732,33 +15083,157 @@ class AiLiveAssist {
|
|
|
14732
15083
|
}
|
|
14733
15084
|
}
|
|
14734
15085
|
readStagedPendingLaunch() {
|
|
14735
|
-
let raw = null;
|
|
14736
15086
|
try {
|
|
14737
|
-
|
|
14738
|
-
const
|
|
14739
|
-
|
|
15087
|
+
const v2Raw = localStorage.getItem(this.stagedPendingLaunchStorageKey);
|
|
15088
|
+
const parsedV2 = parseStagedPendingLaunch(v2Raw);
|
|
15089
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15090
|
+
if (v2Raw && !v2) {
|
|
14740
15091
|
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14741
15092
|
}
|
|
14742
|
-
|
|
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;
|
|
14743
15114
|
}
|
|
14744
15115
|
catch {
|
|
14745
15116
|
return null;
|
|
14746
15117
|
}
|
|
14747
15118
|
}
|
|
14748
15119
|
persistStagedPendingLaunch(staged) {
|
|
15120
|
+
let rollback = null;
|
|
14749
15121
|
try {
|
|
14750
|
-
|
|
14751
|
-
|
|
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;
|
|
14752
15197
|
}
|
|
14753
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
|
+
}
|
|
14754
15206
|
return false;
|
|
14755
15207
|
}
|
|
14756
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
|
+
}
|
|
14757
15221
|
pendingLaunchMatches(left, right) {
|
|
14758
|
-
|
|
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);
|
|
14759
15230
|
}
|
|
14760
15231
|
stagePendingLaunch(pendingLaunch, authority, sourceOwnerId) {
|
|
14761
|
-
|
|
15232
|
+
const boundPendingLaunch = this.bootstrap
|
|
15233
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15234
|
+
: null;
|
|
15235
|
+
if (!boundPendingLaunch ||
|
|
15236
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(sourceOwnerId) ||
|
|
14762
15237
|
sourceOwnerId === authority.ownerId) {
|
|
14763
15238
|
return false;
|
|
14764
15239
|
}
|
|
@@ -14769,35 +15244,82 @@ class AiLiveAssist {
|
|
|
14769
15244
|
if (authorityOrder < 0 ||
|
|
14770
15245
|
(authorityOrder === 0 &&
|
|
14771
15246
|
(existing?.sourceOwnerId !== sourceOwnerId ||
|
|
14772
|
-
!this.pendingLaunchMatches(existing.pendingLaunch
|
|
15247
|
+
!this.pendingLaunchMatches(!pendingLaunchIsBound(existing.pendingLaunch) && this.bootstrap
|
|
15248
|
+
? (bindPendingLaunchIntent(existing.pendingLaunch, this.bootstrap) ?? existing.pendingLaunch)
|
|
15249
|
+
: existing.pendingLaunch, boundPendingLaunch) ||
|
|
14773
15250
|
existing.state !== "prepared"))) {
|
|
14774
15251
|
return false;
|
|
14775
15252
|
}
|
|
14776
15253
|
return this.persistStagedPendingLaunch({
|
|
14777
|
-
v:
|
|
15254
|
+
v: 2,
|
|
14778
15255
|
sourceOwnerId,
|
|
14779
15256
|
authority: { ...authority },
|
|
14780
15257
|
state: "prepared",
|
|
14781
15258
|
claimAuthority: null,
|
|
14782
15259
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14783
15260
|
pendingLaunch: {
|
|
14784
|
-
...
|
|
14785
|
-
scopes: [...
|
|
15261
|
+
...boundPendingLaunch,
|
|
15262
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14786
15263
|
},
|
|
14787
15264
|
});
|
|
14788
15265
|
}
|
|
14789
15266
|
clearStagedPendingLaunch(pendingLaunch, authority) {
|
|
14790
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;
|
|
14791
15274
|
if (!staged ||
|
|
15275
|
+
!comparablePending ||
|
|
15276
|
+
!comparableStaged ||
|
|
14792
15277
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14793
|
-
!this.pendingLaunchMatches(
|
|
14794
|
-
return;
|
|
15278
|
+
!this.pendingLaunchMatches(comparableStaged, comparablePending)) {
|
|
15279
|
+
return false;
|
|
14795
15280
|
}
|
|
14796
15281
|
try {
|
|
14797
|
-
|
|
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);
|
|
14798
15320
|
}
|
|
14799
15321
|
catch {
|
|
14800
|
-
|
|
15322
|
+
return false;
|
|
14801
15323
|
}
|
|
14802
15324
|
}
|
|
14803
15325
|
clearOwnedStagedPendingLaunch() {
|
|
@@ -14814,7 +15336,7 @@ class AiLiveAssist {
|
|
|
14814
15336
|
const staged = this.readStagedPendingLaunch();
|
|
14815
15337
|
if (!pendingLaunch ||
|
|
14816
15338
|
!staged ||
|
|
14817
|
-
staged.pendingLaunch
|
|
15339
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch) ||
|
|
14818
15340
|
this.terminalEndIntent?.launchIdempotencyKey !==
|
|
14819
15341
|
pendingLaunch.idempotencyKey) {
|
|
14820
15342
|
return;
|
|
@@ -14825,11 +15347,15 @@ class AiLiveAssist {
|
|
|
14825
15347
|
const pendingLaunch = parseFocusedHandoffPayload(value)?.pendingLaunch;
|
|
14826
15348
|
if (!pendingLaunch)
|
|
14827
15349
|
return true;
|
|
15350
|
+
const boundPendingLaunch = this.bootstrap
|
|
15351
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15352
|
+
: null;
|
|
14828
15353
|
const staged = this.readStagedPendingLaunch();
|
|
14829
|
-
if (!
|
|
15354
|
+
if (!boundPendingLaunch ||
|
|
15355
|
+
!staged ||
|
|
14830
15356
|
staged.state !== "prepared" ||
|
|
14831
15357
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14832
|
-
!this.pendingLaunchMatches(staged.pendingLaunch,
|
|
15358
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, boundPendingLaunch)) {
|
|
14833
15359
|
return false;
|
|
14834
15360
|
}
|
|
14835
15361
|
return this.persistStagedPendingLaunch({
|
|
@@ -14873,6 +15399,12 @@ class AiLiveAssist {
|
|
|
14873
15399
|
}
|
|
14874
15400
|
markStagedPendingLaunchInFlight(pendingLaunch) {
|
|
14875
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;
|
|
14876
15408
|
// AiLiveAssist always installs a TabActivityLock with durable authority.
|
|
14877
15409
|
// Keep prototype-level test harnesses that intentionally omit the lock
|
|
14878
15410
|
// implementation isolated from the persistence contract.
|
|
@@ -14884,40 +15416,54 @@ class AiLiveAssist {
|
|
|
14884
15416
|
}
|
|
14885
15417
|
if (!staged) {
|
|
14886
15418
|
return this.persistStagedPendingLaunch({
|
|
14887
|
-
v: 1,
|
|
15419
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14888
15420
|
sourceOwnerId: randomId("launch_source"),
|
|
14889
15421
|
authority: { ...ownedAuthority },
|
|
14890
15422
|
state: "in_flight",
|
|
14891
15423
|
claimAuthority: { ...ownedAuthority },
|
|
14892
15424
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14893
15425
|
pendingLaunch: {
|
|
14894
|
-
...
|
|
14895
|
-
scopes: [...
|
|
15426
|
+
...durablePendingLaunch,
|
|
15427
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14896
15428
|
},
|
|
14897
15429
|
});
|
|
14898
15430
|
}
|
|
15431
|
+
const comparableStagedPendingLaunch = pendingLaunchIsBound(staged.pendingLaunch)
|
|
15432
|
+
? staged.pendingLaunch
|
|
15433
|
+
: this.bootstrap
|
|
15434
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15435
|
+
: null;
|
|
14899
15436
|
if (staged.pendingLaunch.idempotencyKey !== pendingLaunch.idempotencyKey ||
|
|
14900
15437
|
!staged.claimAuthority ||
|
|
14901
15438
|
compareTransferAuthority(staged.claimAuthority, ownedAuthority) !== 0 ||
|
|
14902
|
-
(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))) {
|
|
14903
15444
|
return false;
|
|
14904
15445
|
}
|
|
14905
|
-
if (staged.state === "in_flight"
|
|
15446
|
+
if (staged.state === "in_flight" &&
|
|
15447
|
+
staged.v === (boundPendingLaunch ? 2 : 1) &&
|
|
15448
|
+
this.pendingLaunchMatches(staged.pendingLaunch, durablePendingLaunch)) {
|
|
14906
15449
|
return true;
|
|
15450
|
+
}
|
|
14907
15451
|
return this.persistStagedPendingLaunch({
|
|
14908
15452
|
...staged,
|
|
15453
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14909
15454
|
state: "in_flight",
|
|
14910
15455
|
pendingLaunch: {
|
|
14911
|
-
...
|
|
14912
|
-
scopes: [...
|
|
15456
|
+
...durablePendingLaunch,
|
|
15457
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14913
15458
|
},
|
|
14914
15459
|
});
|
|
14915
15460
|
}
|
|
14916
15461
|
acceptStagedPendingLaunch(pendingLaunch, result) {
|
|
14917
15462
|
const staged = this.readStagedPendingLaunch();
|
|
14918
|
-
if (!staged
|
|
14919
|
-
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15463
|
+
if (!staged)
|
|
14920
15464
|
return true;
|
|
15465
|
+
if (!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15466
|
+
return false;
|
|
14921
15467
|
}
|
|
14922
15468
|
const ownedAuthority = this.lock.ownedAuthority?.() ?? null;
|
|
14923
15469
|
const stored = this.readResume();
|
|
@@ -14941,17 +15487,11 @@ class AiLiveAssist {
|
|
|
14941
15487
|
})) {
|
|
14942
15488
|
return false;
|
|
14943
15489
|
}
|
|
14944
|
-
|
|
14945
|
-
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14946
|
-
return localStorage.getItem(this.stagedPendingLaunchStorageKey) === null;
|
|
14947
|
-
}
|
|
14948
|
-
catch {
|
|
14949
|
-
return false;
|
|
14950
|
-
}
|
|
15490
|
+
return this.clearStagedPendingLaunch(staged.pendingLaunch, staged.authority);
|
|
14951
15491
|
}
|
|
14952
15492
|
hasStagedPendingLaunch(pendingLaunch) {
|
|
14953
|
-
|
|
14954
|
-
|
|
15493
|
+
const staged = this.readStagedPendingLaunch();
|
|
15494
|
+
return Boolean(staged && this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch));
|
|
14955
15495
|
}
|
|
14956
15496
|
storeTransferAuthority(authority) {
|
|
14957
15497
|
const current = this.readTransferAuthority();
|
|
@@ -15029,12 +15569,21 @@ class AiLiveAssist {
|
|
|
15029
15569
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15030
15570
|
return null;
|
|
15031
15571
|
try {
|
|
15032
|
-
const
|
|
15033
|
-
const
|
|
15034
|
-
|
|
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) {
|
|
15035
15576
|
sessionStorage.removeItem(this.freshAiReturnProvisionalStorageKey);
|
|
15036
15577
|
}
|
|
15037
|
-
|
|
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;
|
|
15038
15587
|
}
|
|
15039
15588
|
catch {
|
|
15040
15589
|
return null;
|
|
@@ -15044,9 +15593,54 @@ class AiLiveAssist {
|
|
|
15044
15593
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15045
15594
|
return false;
|
|
15046
15595
|
try {
|
|
15047
|
-
|
|
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
|
+
}
|
|
15048
15642
|
return (canonicalJson(this.readFreshAiReturnProvisional()) ===
|
|
15049
|
-
canonicalJson(
|
|
15643
|
+
canonicalJson(normalized));
|
|
15050
15644
|
}
|
|
15051
15645
|
catch {
|
|
15052
15646
|
return false;
|
|
@@ -15056,19 +15650,21 @@ class AiLiveAssist {
|
|
|
15056
15650
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15057
15651
|
return;
|
|
15058
15652
|
try {
|
|
15059
|
-
const
|
|
15653
|
+
const storageKey = expected?.v === 1
|
|
15654
|
+
? this.legacyFreshAiReturnProvisionalStorageKey
|
|
15655
|
+
: this.freshAiReturnProvisionalStorageKey;
|
|
15656
|
+
const serialized = sessionStorage.getItem(storageKey);
|
|
15060
15657
|
if (!serialized)
|
|
15061
15658
|
return;
|
|
15062
15659
|
if (expected) {
|
|
15063
15660
|
const stored = parseFreshAiReturnProvisional(JSON.parse(serialized));
|
|
15064
15661
|
if (canonicalJson(stored) !== canonicalJson(expected))
|
|
15065
15662
|
return;
|
|
15066
|
-
if (sessionStorage.getItem(
|
|
15067
|
-
serialized) {
|
|
15663
|
+
if (sessionStorage.getItem(storageKey) !== serialized) {
|
|
15068
15664
|
return;
|
|
15069
15665
|
}
|
|
15070
15666
|
}
|
|
15071
|
-
sessionStorage.removeItem(
|
|
15667
|
+
sessionStorage.removeItem(storageKey);
|
|
15072
15668
|
}
|
|
15073
15669
|
catch {
|
|
15074
15670
|
/* noop */
|
|
@@ -15659,10 +16255,7 @@ class AiLiveAssist {
|
|
|
15659
16255
|
!this.view ||
|
|
15660
16256
|
(record &&
|
|
15661
16257
|
(!record.tabId ||
|
|
15662
|
-
record.
|
|
15663
|
-
(0, exports.resolveAiGuideProtocolVersion)(this.bootstrap) ||
|
|
15664
|
-
record.experienceMode !==
|
|
15665
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16258
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15666
16259
|
(pendingLaunch &&
|
|
15667
16260
|
(this.visitorActiveTakeoverOnly ||
|
|
15668
16261
|
((this.bootstrap.experienceMode ?? "standard") === "voice_presence" &&
|
|
@@ -15698,10 +16291,11 @@ class AiLiveAssist {
|
|
|
15698
16291
|
this.lock.release();
|
|
15699
16292
|
};
|
|
15700
16293
|
const preparedMatches = record
|
|
15701
|
-
? prepared?.resume
|
|
16294
|
+
? Boolean(prepared?.resume &&
|
|
16295
|
+
this.sameResumeAuthorityIdentity(prepared.resume, record))
|
|
15702
16296
|
: Boolean(pendingLaunch &&
|
|
15703
|
-
prepared?.pendingLaunch
|
|
15704
|
-
|
|
16297
|
+
prepared?.pendingLaunch &&
|
|
16298
|
+
this.pendingLaunchMatches(prepared.pendingLaunch, pendingLaunch));
|
|
15705
16299
|
if (!payload ||
|
|
15706
16300
|
(!record && !pendingLaunch) ||
|
|
15707
16301
|
!this.bootstrap ||
|
|
@@ -15714,10 +16308,7 @@ class AiLiveAssist {
|
|
|
15714
16308
|
!preparedMatches ||
|
|
15715
16309
|
(record &&
|
|
15716
16310
|
(!record.tabId ||
|
|
15717
|
-
record.
|
|
15718
|
-
(0, exports.resolveAiGuideProtocolVersion)(this.bootstrap) ||
|
|
15719
|
-
record.experienceMode !==
|
|
15720
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16311
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15721
16312
|
(pendingLaunch && this.visitorActiveTakeoverOnly)) {
|
|
15722
16313
|
abandonUnusableCommit();
|
|
15723
16314
|
return;
|
|
@@ -15729,7 +16320,7 @@ class AiLiveAssist {
|
|
|
15729
16320
|
return;
|
|
15730
16321
|
}
|
|
15731
16322
|
if (this.lastLaunch &&
|
|
15732
|
-
this.lastLaunch
|
|
16323
|
+
!this.pendingLaunchMatches(this.lastLaunch, stagedPending)) {
|
|
15733
16324
|
this.clearStagedPendingLaunch(stagedPending, authority);
|
|
15734
16325
|
}
|
|
15735
16326
|
else {
|
|
@@ -15741,6 +16332,12 @@ class AiLiveAssist {
|
|
|
15741
16332
|
return;
|
|
15742
16333
|
}
|
|
15743
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
|
+
}
|
|
15744
16341
|
const effectivePayload = record &&
|
|
15745
16342
|
storedResume?.sessionId === record.sessionId &&
|
|
15746
16343
|
storedResume.resumeCredential !== record.resumeCredential
|
|
@@ -15781,11 +16378,11 @@ class AiLiveAssist {
|
|
|
15781
16378
|
completeFocusedTabRelinquish(value) {
|
|
15782
16379
|
const payload = parseFocusedHandoffPayload(value);
|
|
15783
16380
|
const sameResume = Boolean(payload?.resume &&
|
|
15784
|
-
this.focusedHandoffInFlight?.resume
|
|
15785
|
-
|
|
16381
|
+
this.focusedHandoffInFlight?.resume &&
|
|
16382
|
+
this.sameResumeAuthorityIdentity(this.focusedHandoffInFlight.resume, payload.resume));
|
|
15786
16383
|
const samePendingLaunch = Boolean(payload?.pendingLaunch &&
|
|
15787
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15788
|
-
|
|
16384
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16385
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, payload.pendingLaunch));
|
|
15789
16386
|
if (!payload || (!sameResume && !samePendingLaunch)) {
|
|
15790
16387
|
return;
|
|
15791
16388
|
}
|
|
@@ -15816,8 +16413,8 @@ class AiLiveAssist {
|
|
|
15816
16413
|
this.clearStagedPendingLaunch(pendingLaunch, authority);
|
|
15817
16414
|
}
|
|
15818
16415
|
if (pendingLaunch &&
|
|
15819
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15820
|
-
|
|
16416
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16417
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, pendingLaunch)) {
|
|
15821
16418
|
this.focusedHandoffInFlight = null;
|
|
15822
16419
|
if (this.terminalRequestPending ||
|
|
15823
16420
|
this.ended ||
|
|
@@ -15839,7 +16436,7 @@ class AiLiveAssist {
|
|
|
15839
16436
|
queueMicrotask(() => {
|
|
15840
16437
|
const retry = this.lastLaunch;
|
|
15841
16438
|
if (!retry ||
|
|
15842
|
-
retry
|
|
16439
|
+
!this.pendingLaunchMatches(retry, pendingLaunch) ||
|
|
15843
16440
|
this.terminalRequestPending ||
|
|
15844
16441
|
this.ended ||
|
|
15845
16442
|
this.destroyed ||
|
|
@@ -15848,7 +16445,9 @@ class AiLiveAssist {
|
|
|
15848
16445
|
return;
|
|
15849
16446
|
}
|
|
15850
16447
|
this.lastLaunch = null;
|
|
15851
|
-
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey
|
|
16448
|
+
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey, {
|
|
16449
|
+
pendingLaunch: retry,
|
|
16450
|
+
});
|
|
15852
16451
|
});
|
|
15853
16452
|
return;
|
|
15854
16453
|
}
|
|
@@ -15914,7 +16513,7 @@ class AiLiveAssist {
|
|
|
15914
16513
|
this.view &&
|
|
15915
16514
|
!this.visitorActiveTakeoverOnly) {
|
|
15916
16515
|
this.lastLaunch = null;
|
|
15917
|
-
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey);
|
|
16516
|
+
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey, { pendingLaunch: deferredLaunch });
|
|
15918
16517
|
return;
|
|
15919
16518
|
}
|
|
15920
16519
|
this.lock.release();
|