@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/lib/AiLiveAssist.js
CHANGED
|
@@ -551,9 +551,71 @@ const versionAtLeast = (current, minimum) => {
|
|
|
551
551
|
};
|
|
552
552
|
export const resolveAiGuideProtocolVersion = (bootstrap) => bootstrap.experienceMode === "voice_presence" ||
|
|
553
553
|
(bootstrap.minimumProtocolVersion ?? 1) >= 2 ||
|
|
554
|
-
bootstrap.protocol.min >= 2
|
|
554
|
+
bootstrap.protocol.min >= 2 ||
|
|
555
|
+
bootstrap.protocol.max >= 2
|
|
555
556
|
? 2
|
|
556
557
|
: 1;
|
|
558
|
+
const resumeProtocolIsSupported = (record, bootstrap) => {
|
|
559
|
+
const experienceMode = bootstrap.experienceMode ?? "standard";
|
|
560
|
+
if (record.experienceMode !== experienceMode)
|
|
561
|
+
return false;
|
|
562
|
+
if (record.protocolVersion === resolveAiGuideProtocolVersion(bootstrap)) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
// Prefer V2 for fresh standard launches without abandoning a V1 session
|
|
566
|
+
// while the published configuration and runtime still admit V1.
|
|
567
|
+
return (record.experienceMode === "standard" &&
|
|
568
|
+
record.protocolVersion === 1 &&
|
|
569
|
+
(bootstrap.minimumProtocolVersion ?? 1) <= 1 &&
|
|
570
|
+
bootstrap.protocol.min <= 1 &&
|
|
571
|
+
bootstrap.protocol.max >= 1);
|
|
572
|
+
};
|
|
573
|
+
const protocolVersionIsAdmitted = (protocolVersion, bootstrap) => protocolVersion >= (bootstrap.minimumProtocolVersion ?? 1) &&
|
|
574
|
+
protocolVersion >= bootstrap.protocol.min &&
|
|
575
|
+
protocolVersion <= bootstrap.protocol.max;
|
|
576
|
+
const pendingLaunchIsBound = (pendingLaunch) => (pendingLaunch.protocolVersion === 1 ||
|
|
577
|
+
pendingLaunch.protocolVersion === 2) &&
|
|
578
|
+
(pendingLaunch.experienceMode === "standard" ||
|
|
579
|
+
pendingLaunch.experienceMode === "voice_presence");
|
|
580
|
+
const bindPendingLaunchIntent = (pendingLaunch, bootstrap) => {
|
|
581
|
+
const effectiveExperienceMode = bootstrap.experienceMode ?? "standard";
|
|
582
|
+
let protocolVersion = pendingLaunch.protocolVersion;
|
|
583
|
+
let experienceMode = pendingLaunch.experienceMode;
|
|
584
|
+
const hasProtocolVersion = protocolVersion === 1 || protocolVersion === 2;
|
|
585
|
+
const hasExperienceMode = experienceMode === "standard" || experienceMode === "voice_presence";
|
|
586
|
+
if (hasProtocolVersion !== hasExperienceMode)
|
|
587
|
+
return null;
|
|
588
|
+
if (!hasProtocolVersion && !hasExperienceMode) {
|
|
589
|
+
// A legacy record does not preserve its original protocol body. Current
|
|
590
|
+
// configuration can never prove the historical request, even when the
|
|
591
|
+
// present experience is voice presence, so its idempotency key must not be
|
|
592
|
+
// replayed with inferred authority.
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
if ((protocolVersion !== 1 && protocolVersion !== 2) ||
|
|
596
|
+
(experienceMode !== "standard" && experienceMode !== "voice_presence") ||
|
|
597
|
+
experienceMode !== effectiveExperienceMode ||
|
|
598
|
+
!protocolVersionIsAdmitted(protocolVersion, bootstrap) ||
|
|
599
|
+
(protocolVersion === 1 && experienceMode !== "standard") ||
|
|
600
|
+
(experienceMode === "voice_presence" &&
|
|
601
|
+
(protocolVersion !== 2 || pendingLaunch.mode !== "voice"))) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
mode: pendingLaunch.mode,
|
|
606
|
+
scopes: [...pendingLaunch.scopes],
|
|
607
|
+
idempotencyKey: pendingLaunch.idempotencyKey,
|
|
608
|
+
protocolVersion,
|
|
609
|
+
experienceMode,
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
const freshPendingLaunchIntent = (mode, scopes, idempotencyKey, bootstrap) => ({
|
|
613
|
+
mode,
|
|
614
|
+
scopes: [...scopes],
|
|
615
|
+
idempotencyKey,
|
|
616
|
+
protocolVersion: resolveAiGuideProtocolVersion(bootstrap),
|
|
617
|
+
experienceMode: bootstrap.experienceMode ?? "standard",
|
|
618
|
+
});
|
|
557
619
|
const parseTerminalEndIntent = (value) => {
|
|
558
620
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
559
621
|
return null;
|
|
@@ -588,9 +650,13 @@ const parseFreshAiReturnProvisional = (value) => {
|
|
|
588
650
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
589
651
|
return null;
|
|
590
652
|
const raw = value;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
653
|
+
const recordVersion = raw.v === 1 || raw.v === 2 ? raw.v : null;
|
|
654
|
+
const scopes = Array.isArray(raw.scopes) ? raw.scopes : [];
|
|
655
|
+
const expectedKeys = recordVersion === 1
|
|
656
|
+
? "conversationDisplayId,expiresAt,launchIdempotencyKey,linkId,mode,phase,sessionId,v"
|
|
657
|
+
: "conversationDisplayId,experienceMode,expiresAt,launchIdempotencyKey,linkId,mode,phase,protocolVersion,scopes,sessionId,v";
|
|
658
|
+
if (recordVersion === null ||
|
|
659
|
+
Object.keys(raw).sort().join(",") !== expectedKeys ||
|
|
594
660
|
!PUBLIC_REQUEST_ID_PATTERN.test(raw.linkId ?? "") ||
|
|
595
661
|
!/^[1-9]\d{0,18}$/.test(raw.conversationDisplayId ?? "") ||
|
|
596
662
|
(raw.mode !== "text" && raw.mode !== "voice") ||
|
|
@@ -605,19 +671,42 @@ const parseFreshAiReturnProvisional = (value) => {
|
|
|
605
671
|
(raw.launchIdempotencyKey === null || raw.sessionId !== null)) ||
|
|
606
672
|
(raw.phase === "launched" &&
|
|
607
673
|
(raw.launchIdempotencyKey === null || raw.sessionId === null)) ||
|
|
674
|
+
(recordVersion === 2 &&
|
|
675
|
+
raw.phase === "released" &&
|
|
676
|
+
(raw.protocolVersion !== null ||
|
|
677
|
+
raw.experienceMode !== null ||
|
|
678
|
+
raw.scopes !== null)) ||
|
|
679
|
+
(recordVersion === 2 &&
|
|
680
|
+
raw.phase !== "released" &&
|
|
681
|
+
((raw.protocolVersion !== 1 && raw.protocolVersion !== 2) ||
|
|
682
|
+
(raw.experienceMode !== "standard" &&
|
|
683
|
+
raw.experienceMode !== "voice_presence") ||
|
|
684
|
+
(raw.protocolVersion === 1 && raw.experienceMode !== "standard") ||
|
|
685
|
+
(raw.experienceMode === "voice_presence" &&
|
|
686
|
+
(raw.protocolVersion !== 2 || raw.mode !== "voice")) ||
|
|
687
|
+
!Array.isArray(raw.scopes) ||
|
|
688
|
+
scopes.length > CONSENT_SCOPES.size ||
|
|
689
|
+
scopes.some((scope) => typeof scope !== "string" ||
|
|
690
|
+
!CONSENT_SCOPES.has(scope)) ||
|
|
691
|
+
new Set(scopes).size !== scopes.length)) ||
|
|
608
692
|
!Number.isSafeInteger(raw.expiresAt) ||
|
|
609
693
|
(raw.expiresAt ?? 0) <= Date.now() ||
|
|
610
694
|
(raw.expiresAt ?? 0) > Date.now() + STORAGE_TTL_MS) {
|
|
611
695
|
return null;
|
|
612
696
|
}
|
|
613
697
|
return {
|
|
614
|
-
v:
|
|
698
|
+
v: recordVersion,
|
|
615
699
|
linkId: raw.linkId,
|
|
616
700
|
conversationDisplayId: raw.conversationDisplayId,
|
|
617
701
|
mode: raw.mode,
|
|
618
702
|
phase: raw.phase,
|
|
619
703
|
launchIdempotencyKey: raw.launchIdempotencyKey ?? null,
|
|
620
704
|
sessionId: raw.sessionId ?? null,
|
|
705
|
+
protocolVersion: recordVersion === 2 ? (raw.protocolVersion ?? null) : null,
|
|
706
|
+
experienceMode: recordVersion === 2 ? (raw.experienceMode ?? null) : null,
|
|
707
|
+
scopes: recordVersion === 2 && raw.phase !== "released"
|
|
708
|
+
? [...scopes]
|
|
709
|
+
: null,
|
|
621
710
|
expiresAt: raw.expiresAt,
|
|
622
711
|
};
|
|
623
712
|
};
|
|
@@ -654,6 +743,16 @@ const parseResumeRecord = (value) => {
|
|
|
654
743
|
const record = JSON.parse(value);
|
|
655
744
|
const scopes = Array.isArray(record.scopes) ? record.scopes : [];
|
|
656
745
|
const locale = typeof record.locale === "string" ? canonicalLocale(record.locale) : null;
|
|
746
|
+
const protocolFieldsAbsent = record.protocolVersion === undefined &&
|
|
747
|
+
record.experienceMode === undefined;
|
|
748
|
+
const protocolBindingValid = protocolFieldsAbsent ||
|
|
749
|
+
((record.protocolVersion === 1 || record.protocolVersion === 2) &&
|
|
750
|
+
(record.experienceMode === "standard" ||
|
|
751
|
+
record.experienceMode === "voice_presence") &&
|
|
752
|
+
(record.protocolVersion !== 1 ||
|
|
753
|
+
record.experienceMode === "standard") &&
|
|
754
|
+
(record.experienceMode !== "voice_presence" ||
|
|
755
|
+
(record.protocolVersion === 2 && record.mode === "voice")));
|
|
657
756
|
if (!PUBLIC_REQUEST_ID_PATTERN.test(record.sessionId ?? "") ||
|
|
658
757
|
!isValidAssistSessionGrant(record.resumeCredential) ||
|
|
659
758
|
!["text", "voice"].includes(record.mode ?? "") ||
|
|
@@ -662,6 +761,7 @@ const parseResumeRecord = (value) => {
|
|
|
662
761
|
scopes.some((scope) => typeof scope !== "string" ||
|
|
663
762
|
!CONSENT_SCOPES.has(scope)) ||
|
|
664
763
|
new Set(scopes).size !== scopes.length ||
|
|
764
|
+
!protocolBindingValid ||
|
|
665
765
|
!locale ||
|
|
666
766
|
typeof record.expiresAt !== "number" ||
|
|
667
767
|
!Number.isFinite(record.expiresAt) ||
|
|
@@ -895,10 +995,10 @@ const parseResumeRecord = (value) => {
|
|
|
895
995
|
? record.visualConsentGranted
|
|
896
996
|
: record.scopes.includes("visual_context"),
|
|
897
997
|
manualTurn: record.manualTurn === true,
|
|
898
|
-
protocolVersion:
|
|
899
|
-
experienceMode:
|
|
900
|
-
? "
|
|
901
|
-
:
|
|
998
|
+
protocolVersion: protocolFieldsAbsent ? 1 : record.protocolVersion,
|
|
999
|
+
experienceMode: protocolFieldsAbsent
|
|
1000
|
+
? "standard"
|
|
1001
|
+
: record.experienceMode,
|
|
902
1002
|
fallbackIntent: record.fallbackIntent === "standard_assist" ||
|
|
903
1003
|
record.fallbackIntent === "human_handoff"
|
|
904
1004
|
? record.fallbackIntent
|
|
@@ -977,11 +1077,62 @@ const parseTranscript = (value) => {
|
|
|
977
1077
|
}
|
|
978
1078
|
return entries;
|
|
979
1079
|
};
|
|
1080
|
+
const parsePendingLaunchIntent = (value, recordVersion) => {
|
|
1081
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1082
|
+
return null;
|
|
1083
|
+
const candidate = value;
|
|
1084
|
+
const scopes = Array.isArray(candidate.scopes) ? candidate.scopes : [];
|
|
1085
|
+
const expectedKeys = recordVersion === 1
|
|
1086
|
+
? ["idempotencyKey", "mode", "scopes"]
|
|
1087
|
+
: [
|
|
1088
|
+
"experienceMode",
|
|
1089
|
+
"idempotencyKey",
|
|
1090
|
+
"mode",
|
|
1091
|
+
"protocolVersion",
|
|
1092
|
+
"scopes",
|
|
1093
|
+
];
|
|
1094
|
+
if (canonicalJson(Object.keys(candidate).sort()) !==
|
|
1095
|
+
canonicalJson(expectedKeys) ||
|
|
1096
|
+
(candidate.mode !== "text" && candidate.mode !== "voice") ||
|
|
1097
|
+
!Array.isArray(candidate.scopes) ||
|
|
1098
|
+
typeof candidate.idempotencyKey !== "string" ||
|
|
1099
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(candidate.idempotencyKey) ||
|
|
1100
|
+
scopes.length > CONSENT_SCOPES.size ||
|
|
1101
|
+
scopes.some((scope) => typeof scope !== "string" || !CONSENT_SCOPES.has(scope)) ||
|
|
1102
|
+
new Set(scopes).size !== scopes.length) {
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
const base = {
|
|
1106
|
+
mode: candidate.mode,
|
|
1107
|
+
scopes: [...scopes],
|
|
1108
|
+
idempotencyKey: candidate.idempotencyKey,
|
|
1109
|
+
};
|
|
1110
|
+
if (recordVersion === 1) {
|
|
1111
|
+
return { ...base, protocolVersion: null, experienceMode: null };
|
|
1112
|
+
}
|
|
1113
|
+
const protocolVersion = candidate.protocolVersion;
|
|
1114
|
+
const experienceMode = candidate.experienceMode;
|
|
1115
|
+
if ((protocolVersion !== 1 && protocolVersion !== 2) ||
|
|
1116
|
+
(experienceMode !== "standard" && experienceMode !== "voice_presence") ||
|
|
1117
|
+
(protocolVersion === 1 && experienceMode !== "standard") ||
|
|
1118
|
+
(experienceMode === "voice_presence" &&
|
|
1119
|
+
(protocolVersion !== 2 || candidate.mode !== "voice"))) {
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
return { ...base, protocolVersion, experienceMode };
|
|
1123
|
+
};
|
|
980
1124
|
const parseFocusedHandoffPayload = (value) => {
|
|
981
1125
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
982
1126
|
return null;
|
|
983
1127
|
const raw = value;
|
|
984
|
-
if (raw.v === 1) {
|
|
1128
|
+
if (raw.v === 1 || raw.v === 2) {
|
|
1129
|
+
const expectedKeys = ["resume", "transcript", "v"];
|
|
1130
|
+
if (raw.pendingLaunch !== undefined)
|
|
1131
|
+
expectedKeys.push("pendingLaunch");
|
|
1132
|
+
if (canonicalJson(Object.keys(raw).sort()) !==
|
|
1133
|
+
canonicalJson(expectedKeys.sort())) {
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
985
1136
|
const resume = raw.resume === null
|
|
986
1137
|
? null
|
|
987
1138
|
: parseResumeRecord(JSON.stringify(raw.resume ?? null));
|
|
@@ -989,30 +1140,17 @@ const parseFocusedHandoffPayload = (value) => {
|
|
|
989
1140
|
return null;
|
|
990
1141
|
let pendingLaunch;
|
|
991
1142
|
if (raw.pendingLaunch !== undefined) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1143
|
+
pendingLaunch =
|
|
1144
|
+
parsePendingLaunchIntent(raw.pendingLaunch, raw.v) ?? undefined;
|
|
1145
|
+
if (resume || !pendingLaunch)
|
|
995
1146
|
return null;
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
(candidate.mode !== "text" && candidate.mode !== "voice") ||
|
|
1001
|
-
!PUBLIC_REQUEST_ID_PATTERN.test(String(candidate.idempotencyKey ?? "")) ||
|
|
1002
|
-
scopes.length > CONSENT_SCOPES.size ||
|
|
1003
|
-
scopes.some((scope) => typeof scope !== "string" ||
|
|
1004
|
-
!CONSENT_SCOPES.has(scope)) ||
|
|
1005
|
-
new Set(scopes).size !== scopes.length) {
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
pendingLaunch = {
|
|
1009
|
-
mode: candidate.mode,
|
|
1010
|
-
scopes: [...scopes],
|
|
1011
|
-
idempotencyKey: candidate.idempotencyKey,
|
|
1012
|
-
};
|
|
1147
|
+
}
|
|
1148
|
+
if (raw.v === 2 &&
|
|
1149
|
+
(!pendingLaunch || !pendingLaunchIsBound(pendingLaunch))) {
|
|
1150
|
+
return null;
|
|
1013
1151
|
}
|
|
1014
1152
|
return {
|
|
1015
|
-
v:
|
|
1153
|
+
v: raw.v,
|
|
1016
1154
|
resume,
|
|
1017
1155
|
transcript: parseTranscript(raw.transcript),
|
|
1018
1156
|
...(pendingLaunch ? { pendingLaunch } : {}),
|
|
@@ -1060,19 +1198,15 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1060
1198
|
const claimAuthority = raw.claimAuthority === null
|
|
1061
1199
|
? null
|
|
1062
1200
|
: parseTransferAuthority(JSON.stringify(raw.claimAuthority ?? null));
|
|
1201
|
+
const recordVersion = raw.v === 1 || raw.v === 2 ? raw.v : null;
|
|
1063
1202
|
const payload = parseFocusedHandoffPayload({
|
|
1064
|
-
v:
|
|
1203
|
+
v: recordVersion,
|
|
1065
1204
|
resume: null,
|
|
1066
1205
|
transcript: [],
|
|
1067
1206
|
pendingLaunch: raw.pendingLaunch,
|
|
1068
1207
|
});
|
|
1069
1208
|
const expiresAt = raw.expiresAt;
|
|
1070
1209
|
const topLevelKeys = Object.keys(raw).sort();
|
|
1071
|
-
const pendingKeys = raw.pendingLaunch &&
|
|
1072
|
-
typeof raw.pendingLaunch === "object" &&
|
|
1073
|
-
!Array.isArray(raw.pendingLaunch)
|
|
1074
|
-
? Object.keys(raw.pendingLaunch).sort()
|
|
1075
|
-
: [];
|
|
1076
1210
|
const authorityKeys = raw.authority &&
|
|
1077
1211
|
typeof raw.authority === "object" &&
|
|
1078
1212
|
!Array.isArray(raw.authority)
|
|
@@ -1093,14 +1227,12 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1093
1227
|
"state",
|
|
1094
1228
|
"v",
|
|
1095
1229
|
]) ||
|
|
1096
|
-
canonicalJson(pendingKeys) !==
|
|
1097
|
-
canonicalJson(["idempotencyKey", "mode", "scopes"]) ||
|
|
1098
1230
|
canonicalJson(authorityKeys) !==
|
|
1099
1231
|
canonicalJson(["ownerId", "revision", "term"]) ||
|
|
1100
1232
|
(raw.claimAuthority !== null &&
|
|
1101
1233
|
canonicalJson(claimAuthorityKeys) !==
|
|
1102
1234
|
canonicalJson(["ownerId", "revision", "term"])) ||
|
|
1103
|
-
|
|
1235
|
+
recordVersion === null ||
|
|
1104
1236
|
typeof raw.sourceOwnerId !== "string" ||
|
|
1105
1237
|
!PUBLIC_REQUEST_ID_PATTERN.test(raw.sourceOwnerId) ||
|
|
1106
1238
|
!authority ||
|
|
@@ -1124,7 +1256,7 @@ const parseStagedPendingLaunch = (value) => {
|
|
|
1124
1256
|
return null;
|
|
1125
1257
|
}
|
|
1126
1258
|
return {
|
|
1127
|
-
v:
|
|
1259
|
+
v: recordVersion,
|
|
1128
1260
|
sourceOwnerId: raw.sourceOwnerId,
|
|
1129
1261
|
authority,
|
|
1130
1262
|
state: raw.state,
|
|
@@ -1517,10 +1649,12 @@ export default class AiLiveAssist {
|
|
|
1517
1649
|
this.storageKey = `__oxvo_ai_live_assist_resume_${this.siteKey}`;
|
|
1518
1650
|
this.transcriptStorageKey = `__oxvo_ai_live_assist_transcript_${this.siteKey}`;
|
|
1519
1651
|
this.transferAuthorityStorageKey = `__oxvo_ai_live_assist_transfer_authority_${this.siteKey}`;
|
|
1520
|
-
this.
|
|
1652
|
+
this.legacyStagedPendingLaunchStorageKey = `__oxvo_ai_live_assist_pending_launch_${this.siteKey}`;
|
|
1653
|
+
this.stagedPendingLaunchStorageKey = `__oxvo_ai_live_assist_pending_launch_v2_${this.siteKey}`;
|
|
1521
1654
|
this.terminalEndStorageKey = `__oxvo_ai_live_assist_terminal_end_${this.siteKey}`;
|
|
1522
1655
|
this.messengerReturnEndStorageKey = `__oxvo_ai_live_assist_handoff_terminal_${this.siteKey}`;
|
|
1523
|
-
this.
|
|
1656
|
+
this.legacyFreshAiReturnProvisionalStorageKey = `__oxvo_ai_live_assist_fresh_return_${this.siteKey}`;
|
|
1657
|
+
this.freshAiReturnProvisionalStorageKey = `__oxvo_ai_live_assist_fresh_return_v2_${this.siteKey}`;
|
|
1524
1658
|
this.pageAssistanceTrackerIdentityStorageKey = `__oxvo_ai_live_assist_page_assistance_tracker_${this.siteKey}`;
|
|
1525
1659
|
this.transcript = this.readTranscript();
|
|
1526
1660
|
this.visitorIdentity = this.resolveVisitorId();
|
|
@@ -1719,8 +1853,7 @@ export default class AiLiveAssist {
|
|
|
1719
1853
|
pendingLaunch &&
|
|
1720
1854
|
(!staged ||
|
|
1721
1855
|
(staged.state === "in_flight" &&
|
|
1722
|
-
staged.pendingLaunch
|
|
1723
|
-
pendingLaunch.idempotencyKey))
|
|
1856
|
+
this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)))
|
|
1724
1857
|
? { ...pendingLaunch, scopes: [...pendingLaunch.scopes] }
|
|
1725
1858
|
: null;
|
|
1726
1859
|
const record = this.readResume();
|
|
@@ -1735,8 +1868,12 @@ export default class AiLiveAssist {
|
|
|
1735
1868
|
this.persistTerminalEndIntent(intent);
|
|
1736
1869
|
if (record)
|
|
1737
1870
|
this.storeTerminalResume(record, intent);
|
|
1738
|
-
else if (this.result)
|
|
1739
|
-
this.
|
|
1871
|
+
else if (this.result) {
|
|
1872
|
+
const retainedResult = this.result;
|
|
1873
|
+
if (!this.captureTerminalAuthorizedResult(retainedResult)) {
|
|
1874
|
+
this.rejectTerminalAuthorizedResult(retainedResult);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1740
1877
|
}
|
|
1741
1878
|
this.terminalRequestPending = true;
|
|
1742
1879
|
if (!this.relinquishModeUpgradeForRecovery(false)) {
|
|
@@ -1900,8 +2037,7 @@ export default class AiLiveAssist {
|
|
|
1900
2037
|
if (resume &&
|
|
1901
2038
|
!terminalIntent &&
|
|
1902
2039
|
!resumedFallback &&
|
|
1903
|
-
(resume
|
|
1904
|
-
resume.protocolVersion !== requestedProtocol ||
|
|
2040
|
+
(!resumeProtocolIsSupported(resume, bootstrap) ||
|
|
1905
2041
|
resume.fallbackIntent !== null)) {
|
|
1906
2042
|
this.clearResume();
|
|
1907
2043
|
resume = null;
|
|
@@ -2625,9 +2761,6 @@ export default class AiLiveAssist {
|
|
|
2625
2761
|
!this.messengerSupportsConfirmedHandoff(messenger)) {
|
|
2626
2762
|
return false;
|
|
2627
2763
|
}
|
|
2628
|
-
bridgeActivationAttempted = true;
|
|
2629
|
-
if (messenger.setAiGuideHandoffEnabled(true) !== true)
|
|
2630
|
-
return false;
|
|
2631
2764
|
if (messenger.ready) {
|
|
2632
2765
|
await this.withMessengerTimeout(messenger.ready(), MESSENGER_LOAD_TIMEOUT_MS);
|
|
2633
2766
|
}
|
|
@@ -2637,6 +2770,9 @@ export default class AiLiveAssist {
|
|
|
2637
2770
|
this.resolveMessenger() !== messenger) {
|
|
2638
2771
|
return false;
|
|
2639
2772
|
}
|
|
2773
|
+
bridgeActivationAttempted = true;
|
|
2774
|
+
if (messenger.setAiGuideHandoffEnabled(true) !== true)
|
|
2775
|
+
return false;
|
|
2640
2776
|
if (this.messengerInitialLauncherVisible === null) {
|
|
2641
2777
|
this.messengerInitialLauncherVisible =
|
|
2642
2778
|
messenger.launcherVisible !== false;
|
|
@@ -3865,13 +4001,16 @@ export default class AiLiveAssist {
|
|
|
3865
4001
|
if (expiresAt <= Date.now())
|
|
3866
4002
|
return null;
|
|
3867
4003
|
return {
|
|
3868
|
-
v:
|
|
4004
|
+
v: 2,
|
|
3869
4005
|
linkId: companion.linkId,
|
|
3870
4006
|
conversationDisplayId: conversationId,
|
|
3871
4007
|
mode,
|
|
3872
4008
|
phase: "released",
|
|
3873
4009
|
launchIdempotencyKey: null,
|
|
3874
4010
|
sessionId: null,
|
|
4011
|
+
protocolVersion: null,
|
|
4012
|
+
experienceMode: null,
|
|
4013
|
+
scopes: null,
|
|
3875
4014
|
expiresAt,
|
|
3876
4015
|
};
|
|
3877
4016
|
}
|
|
@@ -3888,8 +4027,12 @@ export default class AiLiveAssist {
|
|
|
3888
4027
|
}
|
|
3889
4028
|
this.persistFreshAiReturnProvisional({
|
|
3890
4029
|
...provisional,
|
|
4030
|
+
v: 2,
|
|
3891
4031
|
phase: "released",
|
|
3892
4032
|
launchIdempotencyKey: null,
|
|
4033
|
+
protocolVersion: null,
|
|
4034
|
+
experienceMode: null,
|
|
4035
|
+
scopes: null,
|
|
3893
4036
|
});
|
|
3894
4037
|
}
|
|
3895
4038
|
quiesceProvisionalFreshAiReturn() {
|
|
@@ -3903,22 +4046,59 @@ export default class AiLiveAssist {
|
|
|
3903
4046
|
this.view?.setPaused(true);
|
|
3904
4047
|
this.cleanupTransport();
|
|
3905
4048
|
}
|
|
4049
|
+
boundFreshAiReturnProvisional(provisional) {
|
|
4050
|
+
if (provisional.phase === "released" ||
|
|
4051
|
+
provisional.launchIdempotencyKey === null ||
|
|
4052
|
+
provisional.protocolVersion === null ||
|
|
4053
|
+
provisional.experienceMode === null ||
|
|
4054
|
+
provisional.scopes === null ||
|
|
4055
|
+
!this.bootstrap) {
|
|
4056
|
+
return null;
|
|
4057
|
+
}
|
|
4058
|
+
const pendingLaunch = {
|
|
4059
|
+
mode: provisional.mode,
|
|
4060
|
+
scopes: [...provisional.scopes],
|
|
4061
|
+
idempotencyKey: provisional.launchIdempotencyKey,
|
|
4062
|
+
protocolVersion: provisional.protocolVersion,
|
|
4063
|
+
experienceMode: provisional.experienceMode,
|
|
4064
|
+
};
|
|
4065
|
+
const bound = bindPendingLaunchIntent(pendingLaunch, this.bootstrap);
|
|
4066
|
+
return bound && this.pendingLaunchMatches(bound, pendingLaunch)
|
|
4067
|
+
? bound
|
|
4068
|
+
: null;
|
|
4069
|
+
}
|
|
4070
|
+
resumeMatchesFreshAiReturn(resume, pendingLaunch) {
|
|
4071
|
+
return (resume.protocolVersion === pendingLaunch.protocolVersion &&
|
|
4072
|
+
resume.experienceMode === pendingLaunch.experienceMode &&
|
|
4073
|
+
resume.mode === pendingLaunch.mode &&
|
|
4074
|
+
canonicalJson(resume.scopes) === canonicalJson(pendingLaunch.scopes));
|
|
4075
|
+
}
|
|
4076
|
+
resultMatchesFreshAiReturn(result, pendingLaunch) {
|
|
4077
|
+
return (result.schemaVersion === pendingLaunch.protocolVersion &&
|
|
4078
|
+
result.protocolVersion === pendingLaunch.protocolVersion &&
|
|
4079
|
+
result.experienceMode === pendingLaunch.experienceMode &&
|
|
4080
|
+
(result.mode === undefined || result.mode === pendingLaunch.mode));
|
|
4081
|
+
}
|
|
3906
4082
|
async retireProvisionalFreshAiReturn(provisional) {
|
|
3907
4083
|
if (provisional.phase === "released")
|
|
3908
4084
|
return true;
|
|
4085
|
+
const pendingLaunch = this.boundFreshAiReturnProvisional(provisional);
|
|
4086
|
+
if (!pendingLaunch)
|
|
4087
|
+
return false;
|
|
3909
4088
|
const resume = this.readResume();
|
|
3910
4089
|
const result = this.result;
|
|
3911
4090
|
const sessionId = provisional.sessionId ?? resume?.sessionId ?? result?.sessionId ?? null;
|
|
3912
4091
|
if (!sessionId)
|
|
3913
4092
|
return false;
|
|
3914
|
-
const authority = resume?.sessionId === sessionId
|
|
4093
|
+
const authority = resume?.sessionId === sessionId &&
|
|
4094
|
+
this.resumeMatchesFreshAiReturn(resume, pendingLaunch)
|
|
3915
4095
|
? {
|
|
3916
4096
|
resumeCredential: resume.resumeCredential,
|
|
3917
4097
|
protocolVersion: resume.protocolVersion,
|
|
3918
4098
|
}
|
|
3919
4099
|
: result?.sessionId === sessionId &&
|
|
3920
4100
|
typeof result.resumeCredential === "string" &&
|
|
3921
|
-
(result
|
|
4101
|
+
this.resultMatchesFreshAiReturn(result, pendingLaunch)
|
|
3922
4102
|
? {
|
|
3923
4103
|
resumeCredential: result.resumeCredential,
|
|
3924
4104
|
protocolVersion: result.protocolVersion,
|
|
@@ -3976,9 +4156,13 @@ export default class AiLiveAssist {
|
|
|
3976
4156
|
provisional.conversationDisplayId;
|
|
3977
4157
|
const released = {
|
|
3978
4158
|
...provisional,
|
|
4159
|
+
v: 2,
|
|
3979
4160
|
phase: "released",
|
|
3980
4161
|
launchIdempotencyKey: null,
|
|
3981
4162
|
sessionId: null,
|
|
4163
|
+
protocolVersion: null,
|
|
4164
|
+
experienceMode: null,
|
|
4165
|
+
scopes: null,
|
|
3982
4166
|
};
|
|
3983
4167
|
return this.persistFreshAiReturnProvisional(released);
|
|
3984
4168
|
}
|
|
@@ -3999,6 +4183,46 @@ export default class AiLiveAssist {
|
|
|
3999
4183
|
// interrupted must never be replaced by a second fresh operation.
|
|
4000
4184
|
return provisional.phase === "launching" ? "launch" : "blocked";
|
|
4001
4185
|
}
|
|
4186
|
+
if (provisional.protocolVersion === null ||
|
|
4187
|
+
provisional.experienceMode === null ||
|
|
4188
|
+
provisional.scopes === null) {
|
|
4189
|
+
if (provisional.launchIdempotencyKey === null ||
|
|
4190
|
+
!resume ||
|
|
4191
|
+
resume.sessionId !== sessionId) {
|
|
4192
|
+
return "blocked";
|
|
4193
|
+
}
|
|
4194
|
+
const upgraded = {
|
|
4195
|
+
...provisional,
|
|
4196
|
+
v: 2,
|
|
4197
|
+
sessionId,
|
|
4198
|
+
protocolVersion: resume.protocolVersion,
|
|
4199
|
+
experienceMode: resume.experienceMode,
|
|
4200
|
+
scopes: [...resume.scopes],
|
|
4201
|
+
};
|
|
4202
|
+
if (!this.boundFreshAiReturnProvisional(upgraded) ||
|
|
4203
|
+
!this.persistFreshAiReturnProvisional(upgraded)) {
|
|
4204
|
+
return "blocked";
|
|
4205
|
+
}
|
|
4206
|
+
provisional = upgraded;
|
|
4207
|
+
}
|
|
4208
|
+
const pendingLaunch = this.boundFreshAiReturnProvisional(provisional);
|
|
4209
|
+
if (!pendingLaunch)
|
|
4210
|
+
return "blocked";
|
|
4211
|
+
let hasSessionAuthority = false;
|
|
4212
|
+
if (resume?.sessionId === sessionId) {
|
|
4213
|
+
if (!this.resumeMatchesFreshAiReturn(resume, pendingLaunch)) {
|
|
4214
|
+
return "blocked";
|
|
4215
|
+
}
|
|
4216
|
+
hasSessionAuthority = true;
|
|
4217
|
+
}
|
|
4218
|
+
if (this.result?.sessionId === sessionId) {
|
|
4219
|
+
if (!this.resultMatchesFreshAiReturn(this.result, pendingLaunch)) {
|
|
4220
|
+
return "blocked";
|
|
4221
|
+
}
|
|
4222
|
+
hasSessionAuthority = true;
|
|
4223
|
+
}
|
|
4224
|
+
if (!hasSessionAuthority)
|
|
4225
|
+
return "blocked";
|
|
4002
4226
|
if (provisional.phase === "launching") {
|
|
4003
4227
|
provisional = {
|
|
4004
4228
|
...provisional,
|
|
@@ -4200,17 +4424,61 @@ export default class AiLiveAssist {
|
|
|
4200
4424
|
const currentProvisional = this.freshAiReturnProvisionalFor(conversationId);
|
|
4201
4425
|
if (!currentProvisional)
|
|
4202
4426
|
return false;
|
|
4427
|
+
if (currentProvisional.phase === "launching" &&
|
|
4428
|
+
currentProvisional.mode !== mode) {
|
|
4429
|
+
return false;
|
|
4430
|
+
}
|
|
4431
|
+
const persistedLaunchScopes = currentProvisional.phase === "launching"
|
|
4432
|
+
? currentProvisional.scopes
|
|
4433
|
+
: null;
|
|
4434
|
+
if (currentProvisional.phase === "launching" &&
|
|
4435
|
+
(currentProvisional.protocolVersion === null ||
|
|
4436
|
+
currentProvisional.experienceMode === null ||
|
|
4437
|
+
persistedLaunchScopes === null)) {
|
|
4438
|
+
// A legacy Fresh-return record does not contain the complete request
|
|
4439
|
+
// body. Reusing its idempotency key with current bootstrap scopes or
|
|
4440
|
+
// protocol negotiation would create a different operation.
|
|
4441
|
+
return false;
|
|
4442
|
+
}
|
|
4203
4443
|
const launchIdempotencyKey = currentProvisional.phase === "launching"
|
|
4204
4444
|
? currentProvisional.launchIdempotencyKey
|
|
4205
4445
|
: idempotencyKey("launch");
|
|
4206
4446
|
if (!launchIdempotencyKey)
|
|
4207
4447
|
return false;
|
|
4448
|
+
const launchScopes = persistedLaunchScopes
|
|
4449
|
+
? [...persistedLaunchScopes]
|
|
4450
|
+
: [...scopes];
|
|
4451
|
+
const provisionalPendingLaunch = currentProvisional.protocolVersion !== null &&
|
|
4452
|
+
currentProvisional.experienceMode !== null
|
|
4453
|
+
? {
|
|
4454
|
+
mode: currentProvisional.mode,
|
|
4455
|
+
scopes: launchScopes,
|
|
4456
|
+
idempotencyKey: launchIdempotencyKey,
|
|
4457
|
+
protocolVersion: currentProvisional.protocolVersion,
|
|
4458
|
+
experienceMode: currentProvisional.experienceMode,
|
|
4459
|
+
}
|
|
4460
|
+
: {
|
|
4461
|
+
mode: currentProvisional.mode,
|
|
4462
|
+
scopes: launchScopes,
|
|
4463
|
+
idempotencyKey: launchIdempotencyKey,
|
|
4464
|
+
protocolVersion: null,
|
|
4465
|
+
experienceMode: null,
|
|
4466
|
+
};
|
|
4467
|
+
const pendingLaunch = currentProvisional.phase === "launching"
|
|
4468
|
+
? bindPendingLaunchIntent(provisionalPendingLaunch, bootstrap)
|
|
4469
|
+
: freshPendingLaunchIntent(mode, scopes, launchIdempotencyKey, bootstrap);
|
|
4470
|
+
if (!pendingLaunch)
|
|
4471
|
+
return false;
|
|
4208
4472
|
const launching = {
|
|
4209
4473
|
...currentProvisional,
|
|
4210
|
-
|
|
4474
|
+
v: 2,
|
|
4475
|
+
mode: pendingLaunch.mode,
|
|
4211
4476
|
phase: "launching",
|
|
4212
4477
|
launchIdempotencyKey,
|
|
4213
4478
|
sessionId: null,
|
|
4479
|
+
protocolVersion: pendingLaunch.protocolVersion,
|
|
4480
|
+
experienceMode: pendingLaunch.experienceMode,
|
|
4481
|
+
scopes: [...pendingLaunch.scopes],
|
|
4214
4482
|
};
|
|
4215
4483
|
if (!this.persistFreshAiReturnProvisional(launching))
|
|
4216
4484
|
return false;
|
|
@@ -4226,7 +4494,7 @@ export default class AiLiveAssist {
|
|
|
4226
4494
|
inboxId: messengerDescriptor.inboxId,
|
|
4227
4495
|
linkId: this.companion.linkId,
|
|
4228
4496
|
launchIdempotencyKey,
|
|
4229
|
-
mode,
|
|
4497
|
+
mode: pendingLaunch.mode,
|
|
4230
4498
|
previousAiSessionId: this.companion.currentAiSessionId,
|
|
4231
4499
|
surface: this.messengerTransferredSurface,
|
|
4232
4500
|
}
|
|
@@ -4234,10 +4502,11 @@ export default class AiLiveAssist {
|
|
|
4234
4502
|
if (!messengerReturnAuthority)
|
|
4235
4503
|
return false;
|
|
4236
4504
|
this.messengerReturnRetryRemaining = 1;
|
|
4237
|
-
await this.launch(mode, scopes, launchIdempotencyKey, {
|
|
4238
|
-
startMuted: mode === "voice",
|
|
4505
|
+
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, launchIdempotencyKey, {
|
|
4506
|
+
startMuted: pendingLaunch.mode === "voice",
|
|
4239
4507
|
associateCompanion: false,
|
|
4240
4508
|
messengerReturnAuthority,
|
|
4509
|
+
pendingLaunch,
|
|
4241
4510
|
});
|
|
4242
4511
|
this.messengerReturnRetryRemaining = null;
|
|
4243
4512
|
const sessionId = this.result?.sessionId;
|
|
@@ -4432,6 +4701,7 @@ export default class AiLiveAssist {
|
|
|
4432
4701
|
if (this.pushActive)
|
|
4433
4702
|
this.media?.beginPushToTalk();
|
|
4434
4703
|
this.closeMessengerBeforeOpen();
|
|
4704
|
+
await this.prepareMessengerIntegration();
|
|
4435
4705
|
return true;
|
|
4436
4706
|
}
|
|
4437
4707
|
async associateFreshCompanionSession(mode, sessionId) {
|
|
@@ -4555,11 +4825,6 @@ export default class AiLiveAssist {
|
|
|
4555
4825
|
const prepared = await this.withMessengerTimeout(messenger.openHandoffConversation(displayId, options), MESSENGER_LOAD_TIMEOUT_MS);
|
|
4556
4826
|
if (!prepared || !surfaceCurrent())
|
|
4557
4827
|
return;
|
|
4558
|
-
const returnActivated = await this.createMessengerReturnActivationPromise(messenger, displayId);
|
|
4559
|
-
if (!returnActivated || !surfaceCurrent()) {
|
|
4560
|
-
this.closeMessengerBeforeOpen();
|
|
4561
|
-
return;
|
|
4562
|
-
}
|
|
4563
4828
|
const switched = await this.requestCompanionSurface("messenger", mode, idempotencyKey("companion_switch"), result.sessionId);
|
|
4564
4829
|
const switchedCurrent = () => generation === this.sessionEffectGeneration &&
|
|
4565
4830
|
this.result === result &&
|
|
@@ -4627,6 +4892,11 @@ export default class AiLiveAssist {
|
|
|
4627
4892
|
await this.restoreAiAfterFailedSupportSwitch(mode, result.sessionId, previousMuted, previousPushActive);
|
|
4628
4893
|
return;
|
|
4629
4894
|
}
|
|
4895
|
+
const returnActivated = await this.createMessengerReturnActivationPromise(messenger, displayId);
|
|
4896
|
+
if (!returnActivated || !switchedCurrent()) {
|
|
4897
|
+
await this.restoreAiAfterFailedSupportSwitch(mode, result.sessionId, previousMuted, previousPushActive);
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4630
4900
|
this.messenger = messenger;
|
|
4631
4901
|
this.messengerSurfaceActive = true;
|
|
4632
4902
|
this.messengerTransferredConversationId = displayId;
|
|
@@ -6189,9 +6459,27 @@ export default class AiLiveAssist {
|
|
|
6189
6459
|
releaseMessengerReturnIfPreRuntime();
|
|
6190
6460
|
return;
|
|
6191
6461
|
}
|
|
6192
|
-
const
|
|
6462
|
+
const retryIntent = options.pendingLaunch;
|
|
6463
|
+
if (retryIntent &&
|
|
6464
|
+
canonicalJson({ mode, scopes, idempotencyKey: launchIdempotencyKey }) !==
|
|
6465
|
+
canonicalJson({
|
|
6466
|
+
mode: retryIntent.mode,
|
|
6467
|
+
scopes: retryIntent.scopes,
|
|
6468
|
+
idempotencyKey: retryIntent.idempotencyKey,
|
|
6469
|
+
})) {
|
|
6470
|
+
releaseMessengerReturnIfPreRuntime();
|
|
6471
|
+
return;
|
|
6472
|
+
}
|
|
6473
|
+
let durableLaunch = retryIntent
|
|
6474
|
+
? bindPendingLaunchIntent(retryIntent, bootstrap)
|
|
6475
|
+
: freshPendingLaunchIntent(mode, scopes, launchIdempotencyKey, bootstrap);
|
|
6476
|
+
if (!durableLaunch) {
|
|
6477
|
+
this.view.showError(this.view.message("reconnectFailed"), false, false);
|
|
6478
|
+
releaseMessengerReturnIfPreRuntime();
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6481
|
+
const { experienceMode, protocolVersion } = durableLaunch;
|
|
6193
6482
|
const voicePresence = experienceMode === "voice_presence";
|
|
6194
|
-
const protocolVersion = resolveAiGuideProtocolVersion(bootstrap);
|
|
6195
6483
|
if (voicePresence && mode !== "voice") {
|
|
6196
6484
|
this.view.showError("Voice assist requires microphone access.", false, false);
|
|
6197
6485
|
releaseMessengerReturnIfPreRuntime();
|
|
@@ -6212,11 +6500,7 @@ export default class AiLiveAssist {
|
|
|
6212
6500
|
this.visitorActiveTakeoverOnly = false;
|
|
6213
6501
|
}
|
|
6214
6502
|
if (this.visitorActiveTakeoverOnly) {
|
|
6215
|
-
const pendingLaunch =
|
|
6216
|
-
mode,
|
|
6217
|
-
scopes: [...scopes],
|
|
6218
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6219
|
-
};
|
|
6503
|
+
const pendingLaunch = durableLaunch;
|
|
6220
6504
|
this.lastLaunch = pendingLaunch;
|
|
6221
6505
|
this.view.showActiveElsewhere(false);
|
|
6222
6506
|
this.view.showConnecting(true, mode);
|
|
@@ -6230,11 +6514,7 @@ export default class AiLiveAssist {
|
|
|
6230
6514
|
}
|
|
6231
6515
|
this.pageAssistanceAuthorityGeneration += 1;
|
|
6232
6516
|
this.rotatePageAssistanceBindingGeneration();
|
|
6233
|
-
this.lastLaunch = {
|
|
6234
|
-
mode,
|
|
6235
|
-
scopes: [...scopes],
|
|
6236
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6237
|
-
};
|
|
6517
|
+
this.lastLaunch = { ...durableLaunch, scopes: [...durableLaunch.scopes] };
|
|
6238
6518
|
this.launchInFlight = true;
|
|
6239
6519
|
if (!this.lock.acquire()) {
|
|
6240
6520
|
this.view.showActiveElsewhere(false);
|
|
@@ -6292,7 +6572,8 @@ export default class AiLiveAssist {
|
|
|
6292
6572
|
if (voicePresence ||
|
|
6293
6573
|
mode !== "voice" ||
|
|
6294
6574
|
!bootstrap.capabilities?.text ||
|
|
6295
|
-
options.messengerReturnAuthority !== undefined
|
|
6575
|
+
options.messengerReturnAuthority !== undefined ||
|
|
6576
|
+
retryIntent !== undefined) {
|
|
6296
6577
|
throw error;
|
|
6297
6578
|
}
|
|
6298
6579
|
this.releaseMedia(media);
|
|
@@ -6322,22 +6603,20 @@ export default class AiLiveAssist {
|
|
|
6322
6603
|
this.navigationConsentResetCandidate = null;
|
|
6323
6604
|
this.navigationConsentReset = null;
|
|
6324
6605
|
this.navigationConsentResetReady = null;
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6606
|
+
if (!retryIntent) {
|
|
6607
|
+
durableLaunch = {
|
|
6608
|
+
...durableLaunch,
|
|
6609
|
+
mode: activeMode,
|
|
6610
|
+
scopes: [...activeScopes],
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
6613
|
+
this.lastLaunch = { ...durableLaunch, scopes: [...durableLaunch.scopes] };
|
|
6330
6614
|
this.visualConsentGranted = activeScopes.includes("visual_context");
|
|
6331
6615
|
assertConnectionCurrent();
|
|
6332
6616
|
if (this.lock.ownsLatestFocus?.() === false &&
|
|
6333
6617
|
this.relinquishForNewerFocus()) {
|
|
6334
6618
|
return;
|
|
6335
6619
|
}
|
|
6336
|
-
const durableLaunch = {
|
|
6337
|
-
mode: activeMode,
|
|
6338
|
-
scopes: [...activeScopes],
|
|
6339
|
-
idempotencyKey: launchIdempotencyKey,
|
|
6340
|
-
};
|
|
6341
6620
|
if (!this.markStagedPendingLaunchInFlight(durableLaunch)) {
|
|
6342
6621
|
this.releaseMedia(media);
|
|
6343
6622
|
this.cleanupTransport();
|
|
@@ -6382,16 +6661,19 @@ export default class AiLiveAssist {
|
|
|
6382
6661
|
}
|
|
6383
6662
|
}
|
|
6384
6663
|
}
|
|
6664
|
+
if (result.schemaVersion !== protocolVersion ||
|
|
6665
|
+
result.protocolVersion !== protocolVersion ||
|
|
6666
|
+
result.experienceMode !== experienceMode) {
|
|
6667
|
+
throw new RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6668
|
+
}
|
|
6385
6669
|
if (this.terminalRequestPending) {
|
|
6386
|
-
this.captureTerminalAuthorizedResult(result)
|
|
6670
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
6671
|
+
throw new RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6672
|
+
}
|
|
6387
6673
|
runtimeSessionCreated = true;
|
|
6388
6674
|
return;
|
|
6389
6675
|
}
|
|
6390
6676
|
assertConnectionCurrent();
|
|
6391
|
-
if (result.experienceMode !== experienceMode ||
|
|
6392
|
-
result.protocolVersion !== protocolVersion) {
|
|
6393
|
-
throw new RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
6394
|
-
}
|
|
6395
6677
|
if (!this.commitAuthorizedRuntimePath(requestedLaunchPath, true)) {
|
|
6396
6678
|
throw new RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime authorized an invalid page path.", false, null, 502);
|
|
6397
6679
|
}
|
|
@@ -6526,7 +6808,8 @@ export default class AiLiveAssist {
|
|
|
6526
6808
|
this.terminalRequestPending ||
|
|
6527
6809
|
this.ended ||
|
|
6528
6810
|
this.destroyed ||
|
|
6529
|
-
this.lastLaunch
|
|
6811
|
+
!this.lastLaunch ||
|
|
6812
|
+
!this.pendingLaunchMatches(this.lastLaunch, pendingLaunch) ||
|
|
6530
6813
|
this.readResume()) {
|
|
6531
6814
|
return;
|
|
6532
6815
|
}
|
|
@@ -6558,7 +6841,8 @@ export default class AiLiveAssist {
|
|
|
6558
6841
|
this.visitorActiveEligibilityRecheckKey = pendingLaunch.idempotencyKey;
|
|
6559
6842
|
const pendingIntentIsCurrent = () => launchGeneration === this.launchGeneration &&
|
|
6560
6843
|
this.visitorActiveEligibilityRecheckController === controller &&
|
|
6561
|
-
this.lastLaunch
|
|
6844
|
+
Boolean(this.lastLaunch &&
|
|
6845
|
+
this.pendingLaunchMatches(this.lastLaunch, pendingLaunch)) &&
|
|
6562
6846
|
!this.terminalRequestPending &&
|
|
6563
6847
|
!this.ended &&
|
|
6564
6848
|
!this.destroyed &&
|
|
@@ -6618,7 +6902,7 @@ export default class AiLiveAssist {
|
|
|
6618
6902
|
this.visitorActiveTakeoverOnly = false;
|
|
6619
6903
|
this.lastLaunch = null;
|
|
6620
6904
|
this.view?.showActiveElsewhere(false);
|
|
6621
|
-
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, pendingLaunch.idempotencyKey);
|
|
6905
|
+
await this.launch(pendingLaunch.mode, pendingLaunch.scopes, pendingLaunch.idempotencyKey, { pendingLaunch });
|
|
6622
6906
|
return;
|
|
6623
6907
|
}
|
|
6624
6908
|
if (status === "visitor_limited") {
|
|
@@ -6650,7 +6934,8 @@ export default class AiLiveAssist {
|
|
|
6650
6934
|
this.visitorActiveEligibilityRecheck = operation;
|
|
6651
6935
|
}
|
|
6652
6936
|
finishVisitorActiveEligibilityRecheck(pendingLaunch, messageKey) {
|
|
6653
|
-
if (this.lastLaunch
|
|
6937
|
+
if (!this.lastLaunch ||
|
|
6938
|
+
!this.pendingLaunchMatches(this.lastLaunch, pendingLaunch)) {
|
|
6654
6939
|
return;
|
|
6655
6940
|
}
|
|
6656
6941
|
this.lastLaunch = null;
|
|
@@ -6807,8 +7092,15 @@ export default class AiLiveAssist {
|
|
|
6807
7092
|
}
|
|
6808
7093
|
}
|
|
6809
7094
|
}
|
|
7095
|
+
if (result.schemaVersion !== record.protocolVersion ||
|
|
7096
|
+
result.protocolVersion !== record.protocolVersion ||
|
|
7097
|
+
result.experienceMode !== record.experienceMode) {
|
|
7098
|
+
throw new RuntimeClientError("PROTOCOL_INVALID", "The saved voice assist session is no longer compatible.", false, null, 409);
|
|
7099
|
+
}
|
|
6810
7100
|
if (this.terminalRequestPending) {
|
|
6811
|
-
this.captureTerminalAuthorizedResult(result, record)
|
|
7101
|
+
if (!this.captureTerminalAuthorizedResult(result, record)) {
|
|
7102
|
+
throw new RuntimeClientError("PROTOCOL_INVALID", "The saved voice assist session is no longer compatible.", false, null, 409);
|
|
7103
|
+
}
|
|
6812
7104
|
if (modeUpgrade)
|
|
6813
7105
|
this.modeUpgradeTerminalResult = result;
|
|
6814
7106
|
attemptedCredentials.add(result.resumeCredential);
|
|
@@ -6818,8 +7110,6 @@ export default class AiLiveAssist {
|
|
|
6818
7110
|
return;
|
|
6819
7111
|
assertConnectionCurrent();
|
|
6820
7112
|
if (result.sessionId !== record.sessionId ||
|
|
6821
|
-
result.protocolVersion !== record.protocolVersion ||
|
|
6822
|
-
result.experienceMode !== record.experienceMode ||
|
|
6823
7113
|
(result.mode !== undefined &&
|
|
6824
7114
|
result.mode !== "text" &&
|
|
6825
7115
|
result.mode !== "voice") ||
|
|
@@ -13097,7 +13387,7 @@ export default class AiLiveAssist {
|
|
|
13097
13387
|
return;
|
|
13098
13388
|
}
|
|
13099
13389
|
this.messengerReturnRetryRemaining -= 1;
|
|
13100
|
-
const retryAttempt = this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey);
|
|
13390
|
+
const retryAttempt = this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey, { pendingLaunch: this.lastLaunch });
|
|
13101
13391
|
return retryAttempt.finally(() => {
|
|
13102
13392
|
if (this.result && !this.ended) {
|
|
13103
13393
|
this.messengerReturnRetryRemaining = null;
|
|
@@ -13117,7 +13407,7 @@ export default class AiLiveAssist {
|
|
|
13117
13407
|
return this.resume(resume);
|
|
13118
13408
|
}
|
|
13119
13409
|
if (this.lastLaunch) {
|
|
13120
|
-
return this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey);
|
|
13410
|
+
return this.launch(this.lastLaunch.mode, this.lastLaunch.scopes, this.lastLaunch.idempotencyKey, { pendingLaunch: this.lastLaunch });
|
|
13121
13411
|
}
|
|
13122
13412
|
}
|
|
13123
13413
|
scheduleReconnect(force = false) {
|
|
@@ -13447,6 +13737,18 @@ export default class AiLiveAssist {
|
|
|
13447
13737
|
canonicalJson(left.messengerBinding) ===
|
|
13448
13738
|
canonicalJson(right.messengerBinding));
|
|
13449
13739
|
}
|
|
13740
|
+
sameResumeAuthorityIdentity(left, right) {
|
|
13741
|
+
const credentialRotated = left.resumeCredential !== right.resumeCredential;
|
|
13742
|
+
if (!credentialRotated && left.expiresAt !== right.expiresAt)
|
|
13743
|
+
return false;
|
|
13744
|
+
const { resumeCredential: _leftCredential, expiresAt: _leftCredentialExpiry, ...leftAuthority } = left;
|
|
13745
|
+
const { resumeCredential: _rightCredential, expiresAt: _rightCredentialExpiry, ...rightAuthority } = right;
|
|
13746
|
+
void _leftCredential;
|
|
13747
|
+
void _rightCredential;
|
|
13748
|
+
void _leftCredentialExpiry;
|
|
13749
|
+
void _rightCredentialExpiry;
|
|
13750
|
+
return canonicalJson(leftAuthority) === canonicalJson(rightAuthority);
|
|
13751
|
+
}
|
|
13450
13752
|
sameModeUpgradeIntent(left, right) {
|
|
13451
13753
|
return (left?.requestId === right?.requestId &&
|
|
13452
13754
|
left?.resumeIdempotencyKey === right?.resumeIdempotencyKey &&
|
|
@@ -13815,10 +14117,22 @@ export default class AiLiveAssist {
|
|
|
13815
14117
|
this.state("ended");
|
|
13816
14118
|
}
|
|
13817
14119
|
captureTerminalAuthorizedResult(result, sourceRecord = null) {
|
|
13818
|
-
|
|
13819
|
-
(
|
|
13820
|
-
|
|
13821
|
-
|
|
14120
|
+
const pendingLaunch = this.terminalPendingLaunch &&
|
|
14121
|
+
pendingLaunchIsBound(this.terminalPendingLaunch)
|
|
14122
|
+
? this.terminalPendingLaunch
|
|
14123
|
+
: null;
|
|
14124
|
+
const expectedProtocolVersion = sourceRecord?.protocolVersion ??
|
|
14125
|
+
pendingLaunch?.protocolVersion ??
|
|
14126
|
+
result.protocolVersion;
|
|
14127
|
+
const expectedExperienceMode = sourceRecord?.experienceMode ??
|
|
14128
|
+
pendingLaunch?.experienceMode ??
|
|
14129
|
+
result.experienceMode;
|
|
14130
|
+
if (result.schemaVersion !== expectedProtocolVersion ||
|
|
14131
|
+
result.protocolVersion !== expectedProtocolVersion ||
|
|
14132
|
+
result.experienceMode !== expectedExperienceMode ||
|
|
14133
|
+
(sourceRecord &&
|
|
14134
|
+
(result.sessionId !== sourceRecord.sessionId ||
|
|
14135
|
+
result.protocolVersion !== sourceRecord.protocolVersion))) {
|
|
13822
14136
|
return false;
|
|
13823
14137
|
}
|
|
13824
14138
|
const currentIntent = this.terminalEndIntent;
|
|
@@ -13844,6 +14158,22 @@ export default class AiLiveAssist {
|
|
|
13844
14158
|
}
|
|
13845
14159
|
return true;
|
|
13846
14160
|
}
|
|
14161
|
+
retryRejectedTerminalAuthorizedResult(result) {
|
|
14162
|
+
const error = this.rejectTerminalAuthorizedResult(result);
|
|
14163
|
+
return this.retryTerminalEnd(error);
|
|
14164
|
+
}
|
|
14165
|
+
rejectTerminalAuthorizedResult(result) {
|
|
14166
|
+
if (this.result === result)
|
|
14167
|
+
this.result = null;
|
|
14168
|
+
if (this.modeUpgradeTerminalResult === result) {
|
|
14169
|
+
this.modeUpgradeTerminalResult = null;
|
|
14170
|
+
}
|
|
14171
|
+
this.terminalSessionAuthorization = null;
|
|
14172
|
+
this.terminalSessionAuthorizationController = null;
|
|
14173
|
+
const error = new RuntimeClientError("PROTOCOL_INVALID", "The assist runtime returned an incompatible terminal session.", false, null, 409);
|
|
14174
|
+
this.terminalSessionAuthorizationFailure = error;
|
|
14175
|
+
return error;
|
|
14176
|
+
}
|
|
13847
14177
|
completeRuntimeEnd() {
|
|
13848
14178
|
if (this.ended)
|
|
13849
14179
|
return Promise.resolve();
|
|
@@ -13874,8 +14204,9 @@ export default class AiLiveAssist {
|
|
|
13874
14204
|
if (terminalAuthorization) {
|
|
13875
14205
|
return this.waitForTerminalRequestDeadline(terminalAuthorization, this.terminalSessionAuthorizationController).then((authorized) => {
|
|
13876
14206
|
const result = authorized ?? this.result ?? this.modeUpgradeTerminalResult;
|
|
13877
|
-
if (result
|
|
13878
|
-
this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)
|
|
14207
|
+
if (result &&
|
|
14208
|
+
!this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)) {
|
|
14209
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
13879
14210
|
}
|
|
13880
14211
|
return this.finishRuntimeEndAttempt(result);
|
|
13881
14212
|
}, (error) => {
|
|
@@ -13892,7 +14223,9 @@ export default class AiLiveAssist {
|
|
|
13892
14223
|
}
|
|
13893
14224
|
if (this.terminalPendingLaunch) {
|
|
13894
14225
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13895
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14226
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14227
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14228
|
+
}
|
|
13896
14229
|
return this.finishRuntimeEndAttempt(result);
|
|
13897
14230
|
}, (recoveryError) => this.retryTerminalEnd(recoveryError));
|
|
13898
14231
|
}
|
|
@@ -13919,7 +14252,9 @@ export default class AiLiveAssist {
|
|
|
13919
14252
|
return this.finishRuntimeEndAttempt(null);
|
|
13920
14253
|
if (this.terminalPendingLaunch) {
|
|
13921
14254
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13922
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14255
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14256
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14257
|
+
}
|
|
13923
14258
|
return this.finishRuntimeEndAttempt(result);
|
|
13924
14259
|
}, (error) => this.retryTerminalEnd(error));
|
|
13925
14260
|
}
|
|
@@ -14076,13 +14411,21 @@ export default class AiLiveAssist {
|
|
|
14076
14411
|
requestTerminalPendingLaunch(pendingLaunch) {
|
|
14077
14412
|
const intent = this.terminalEndIntent;
|
|
14078
14413
|
if (!intent ||
|
|
14079
|
-
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey
|
|
14080
|
-
!this.markStagedPendingLaunchInFlight(pendingLaunch)) {
|
|
14414
|
+
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey) {
|
|
14081
14415
|
return Promise.reject(new TerminalRequestDeferredError());
|
|
14082
14416
|
}
|
|
14083
14417
|
const controller = new AbortController();
|
|
14084
14418
|
const request = (async () => {
|
|
14085
14419
|
const bootstrap = await this.terminalPendingLaunchBootstrap(controller.signal);
|
|
14420
|
+
const boundPendingLaunch = bindPendingLaunchIntent(pendingLaunch, bootstrap);
|
|
14421
|
+
if (!boundPendingLaunch ||
|
|
14422
|
+
!this.markStagedPendingLaunchInFlight(boundPendingLaunch)) {
|
|
14423
|
+
throw new TerminalRequestDeferredError();
|
|
14424
|
+
}
|
|
14425
|
+
this.terminalPendingLaunch = {
|
|
14426
|
+
...boundPendingLaunch,
|
|
14427
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14428
|
+
};
|
|
14086
14429
|
const replay = await this.replayReadiness.wait();
|
|
14087
14430
|
if (this.destroyed ||
|
|
14088
14431
|
this.ended ||
|
|
@@ -14091,28 +14434,34 @@ export default class AiLiveAssist {
|
|
|
14091
14434
|
throw new TerminalRequestDeferredError();
|
|
14092
14435
|
}
|
|
14093
14436
|
this.replayReadiness.assertCurrent(replay);
|
|
14094
|
-
|
|
14437
|
+
const result = await this.runtime.launch({
|
|
14095
14438
|
siteKey: this.siteKey,
|
|
14096
14439
|
configRevision: bootstrap.configRevision,
|
|
14097
14440
|
path: path(),
|
|
14098
14441
|
tabId: this.tabId,
|
|
14099
|
-
mode:
|
|
14442
|
+
mode: boundPendingLaunch.mode,
|
|
14100
14443
|
anonymousId: this.visitorId(),
|
|
14101
14444
|
replay,
|
|
14102
14445
|
disclosureChecksum: bootstrap.consent.disclosureChecksum,
|
|
14103
|
-
scopes:
|
|
14446
|
+
scopes: boundPendingLaunch.scopes,
|
|
14104
14447
|
locale: this.locale,
|
|
14105
|
-
microphone:
|
|
14448
|
+
microphone: boundPendingLaunch.mode === "voice",
|
|
14106
14449
|
reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)")
|
|
14107
14450
|
.matches,
|
|
14108
|
-
protocolVersion:
|
|
14109
|
-
experienceMode:
|
|
14451
|
+
protocolVersion: boundPendingLaunch.protocolVersion,
|
|
14452
|
+
experienceMode: boundPendingLaunch.experienceMode,
|
|
14110
14453
|
...(this.activeFallback
|
|
14111
14454
|
? { fallbackIntent: this.activeFallback }
|
|
14112
14455
|
: {}),
|
|
14113
14456
|
lowPower: this.lowPowerMode(),
|
|
14114
|
-
idempotencyKey:
|
|
14457
|
+
idempotencyKey: boundPendingLaunch.idempotencyKey,
|
|
14115
14458
|
}, controller.signal);
|
|
14459
|
+
if (result.schemaVersion !== boundPendingLaunch.protocolVersion ||
|
|
14460
|
+
result.protocolVersion !== boundPendingLaunch.protocolVersion ||
|
|
14461
|
+
result.experienceMode !== boundPendingLaunch.experienceMode) {
|
|
14462
|
+
throw new RuntimeClientError("PROTOCOL_INVALID", "The voice assist runtime returned an incompatible session.", false, null, 502);
|
|
14463
|
+
}
|
|
14464
|
+
return result;
|
|
14116
14465
|
})();
|
|
14117
14466
|
return this.waitForTerminalRequestDeadline(request, controller);
|
|
14118
14467
|
}
|
|
@@ -14276,8 +14625,7 @@ export default class AiLiveAssist {
|
|
|
14276
14625
|
const retainTerminalLaunch = Boolean(this.terminalSessionAuthorization &&
|
|
14277
14626
|
this.terminalPendingLaunch &&
|
|
14278
14627
|
staged?.state === "in_flight" &&
|
|
14279
|
-
staged.pendingLaunch.
|
|
14280
|
-
this.terminalPendingLaunch.idempotencyKey);
|
|
14628
|
+
this.pendingLaunchMatches(staged.pendingLaunch, this.terminalPendingLaunch));
|
|
14281
14629
|
if (!retainTerminalLaunch)
|
|
14282
14630
|
this.clearOwnedStagedPendingLaunch();
|
|
14283
14631
|
this.lock?.cancelPendingHandoff?.();
|
|
@@ -14701,15 +15049,18 @@ export default class AiLiveAssist {
|
|
|
14701
15049
|
focusedHandoffPayload() {
|
|
14702
15050
|
this.transcript ?? (this.transcript = []);
|
|
14703
15051
|
const resume = this.readResume();
|
|
15052
|
+
const pendingLaunch = !resume && this.lastLaunch && this.bootstrap
|
|
15053
|
+
? bindPendingLaunchIntent(this.lastLaunch, this.bootstrap)
|
|
15054
|
+
: null;
|
|
14704
15055
|
return {
|
|
14705
|
-
v: 1,
|
|
15056
|
+
v: pendingLaunch ? 2 : 1,
|
|
14706
15057
|
resume,
|
|
14707
15058
|
transcript: this.transcript.map((entry) => ({ ...entry })),
|
|
14708
|
-
...(
|
|
15059
|
+
...(pendingLaunch
|
|
14709
15060
|
? {
|
|
14710
15061
|
pendingLaunch: {
|
|
14711
|
-
...
|
|
14712
|
-
scopes: [...
|
|
15062
|
+
...pendingLaunch,
|
|
15063
|
+
scopes: [...pendingLaunch.scopes],
|
|
14713
15064
|
},
|
|
14714
15065
|
}
|
|
14715
15066
|
: {}),
|
|
@@ -14724,33 +15075,157 @@ export default class AiLiveAssist {
|
|
|
14724
15075
|
}
|
|
14725
15076
|
}
|
|
14726
15077
|
readStagedPendingLaunch() {
|
|
14727
|
-
let raw = null;
|
|
14728
15078
|
try {
|
|
14729
|
-
|
|
14730
|
-
const
|
|
14731
|
-
|
|
15079
|
+
const v2Raw = localStorage.getItem(this.stagedPendingLaunchStorageKey);
|
|
15080
|
+
const parsedV2 = parseStagedPendingLaunch(v2Raw);
|
|
15081
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15082
|
+
if (v2Raw && !v2) {
|
|
14732
15083
|
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14733
15084
|
}
|
|
14734
|
-
|
|
15085
|
+
const legacyRaw = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15086
|
+
const parsedLegacy = parseStagedPendingLaunch(legacyRaw);
|
|
15087
|
+
const legacy = parsedLegacy?.v === 1 ? parsedLegacy : null;
|
|
15088
|
+
if (legacyRaw && !legacy) {
|
|
15089
|
+
localStorage.removeItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15090
|
+
}
|
|
15091
|
+
if (!v2)
|
|
15092
|
+
return legacy;
|
|
15093
|
+
if (!legacy)
|
|
15094
|
+
return v2;
|
|
15095
|
+
const authorityOrder = compareTransferAuthority(v2.authority, legacy.authority);
|
|
15096
|
+
if (authorityOrder !== 0)
|
|
15097
|
+
return authorityOrder > 0 ? v2 : legacy;
|
|
15098
|
+
if (v2.sourceOwnerId !== legacy.sourceOwnerId ||
|
|
15099
|
+
!this.pendingLaunchBaseMatches(v2.pendingLaunch, legacy.pendingLaunch)) {
|
|
15100
|
+
return null;
|
|
15101
|
+
}
|
|
15102
|
+
// A V2 record is the only copy that preserves the exact launch body.
|
|
15103
|
+
// Prefer it for an equal authority; the legacy copy is cleaned only
|
|
15104
|
+
// after a matching V2 write has been verified.
|
|
15105
|
+
return v2;
|
|
14735
15106
|
}
|
|
14736
15107
|
catch {
|
|
14737
15108
|
return null;
|
|
14738
15109
|
}
|
|
14739
15110
|
}
|
|
14740
15111
|
persistStagedPendingLaunch(staged) {
|
|
15112
|
+
let rollback = null;
|
|
14741
15113
|
try {
|
|
14742
|
-
|
|
14743
|
-
|
|
15114
|
+
const serializedPendingLaunch = staged.v === 1
|
|
15115
|
+
? {
|
|
15116
|
+
mode: staged.pendingLaunch.mode,
|
|
15117
|
+
scopes: [...staged.pendingLaunch.scopes],
|
|
15118
|
+
idempotencyKey: staged.pendingLaunch.idempotencyKey,
|
|
15119
|
+
}
|
|
15120
|
+
: staged.pendingLaunch;
|
|
15121
|
+
const storageKey = staged.v === 2
|
|
15122
|
+
? this.stagedPendingLaunchStorageKey
|
|
15123
|
+
: this.legacyStagedPendingLaunchStorageKey;
|
|
15124
|
+
const serialized = JSON.stringify({
|
|
15125
|
+
...staged,
|
|
15126
|
+
pendingLaunch: serializedPendingLaunch,
|
|
15127
|
+
});
|
|
15128
|
+
const previousSerialized = localStorage.getItem(storageKey);
|
|
15129
|
+
const previousLegacySerialized = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15130
|
+
let removedMatchingLegacy = false;
|
|
15131
|
+
rollback = () => {
|
|
15132
|
+
if (localStorage.getItem(storageKey) === serialized) {
|
|
15133
|
+
if (previousSerialized === null)
|
|
15134
|
+
localStorage.removeItem(storageKey);
|
|
15135
|
+
else
|
|
15136
|
+
localStorage.setItem(storageKey, previousSerialized);
|
|
15137
|
+
}
|
|
15138
|
+
if (removedMatchingLegacy &&
|
|
15139
|
+
previousLegacySerialized !== null &&
|
|
15140
|
+
localStorage.getItem(this.legacyStagedPendingLaunchStorageKey) ===
|
|
15141
|
+
null) {
|
|
15142
|
+
localStorage.setItem(this.legacyStagedPendingLaunchStorageKey, previousLegacySerialized);
|
|
15143
|
+
}
|
|
15144
|
+
};
|
|
15145
|
+
localStorage.setItem(storageKey, serialized);
|
|
15146
|
+
const normalizedPendingLaunch = staged.v === 1
|
|
15147
|
+
? {
|
|
15148
|
+
...serializedPendingLaunch,
|
|
15149
|
+
protocolVersion: null,
|
|
15150
|
+
experienceMode: null,
|
|
15151
|
+
}
|
|
15152
|
+
: staged.pendingLaunch;
|
|
15153
|
+
const expected = {
|
|
15154
|
+
...staged,
|
|
15155
|
+
pendingLaunch: normalizedPendingLaunch,
|
|
15156
|
+
};
|
|
15157
|
+
const directReadback = parseStagedPendingLaunch(localStorage.getItem(storageKey));
|
|
15158
|
+
if (canonicalJson(directReadback) !== canonicalJson(expected)) {
|
|
15159
|
+
rollback();
|
|
15160
|
+
return false;
|
|
15161
|
+
}
|
|
15162
|
+
if (staged.v === 2) {
|
|
15163
|
+
const legacySerialized = localStorage.getItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15164
|
+
const legacy = parseStagedPendingLaunch(legacySerialized);
|
|
15165
|
+
const equalAuthority = legacy?.v === 1 &&
|
|
15166
|
+
compareTransferAuthority(legacy.authority, staged.authority) === 0;
|
|
15167
|
+
if (equalAuthority &&
|
|
15168
|
+
(legacy.sourceOwnerId !== staged.sourceOwnerId ||
|
|
15169
|
+
!this.pendingLaunchBaseMatches(legacy.pendingLaunch, staged.pendingLaunch))) {
|
|
15170
|
+
rollback();
|
|
15171
|
+
return false;
|
|
15172
|
+
}
|
|
15173
|
+
if (legacy?.v === 1 &&
|
|
15174
|
+
legacy.sourceOwnerId === staged.sourceOwnerId &&
|
|
15175
|
+
equalAuthority &&
|
|
15176
|
+
this.pendingLaunchBaseMatches(legacy.pendingLaunch, staged.pendingLaunch) &&
|
|
15177
|
+
legacySerialized !== null &&
|
|
15178
|
+
localStorage.getItem(this.legacyStagedPendingLaunchStorageKey) ===
|
|
15179
|
+
legacySerialized) {
|
|
15180
|
+
localStorage.removeItem(this.legacyStagedPendingLaunchStorageKey);
|
|
15181
|
+
removedMatchingLegacy = true;
|
|
15182
|
+
}
|
|
15183
|
+
}
|
|
15184
|
+
const persisted = canonicalJson(this.readStagedPendingLaunch()) ===
|
|
15185
|
+
canonicalJson(expected);
|
|
15186
|
+
if (!persisted)
|
|
15187
|
+
rollback();
|
|
15188
|
+
return persisted;
|
|
14744
15189
|
}
|
|
14745
15190
|
catch {
|
|
15191
|
+
try {
|
|
15192
|
+
rollback?.();
|
|
15193
|
+
}
|
|
15194
|
+
catch {
|
|
15195
|
+
// Storage is already unavailable. Leave the launch unclaimed and
|
|
15196
|
+
// fail closed rather than trusting a partially observed write.
|
|
15197
|
+
}
|
|
14746
15198
|
return false;
|
|
14747
15199
|
}
|
|
14748
15200
|
}
|
|
15201
|
+
pendingLaunchBaseMatches(left, right) {
|
|
15202
|
+
return (canonicalJson({
|
|
15203
|
+
mode: left.mode,
|
|
15204
|
+
scopes: left.scopes,
|
|
15205
|
+
idempotencyKey: left.idempotencyKey,
|
|
15206
|
+
}) ===
|
|
15207
|
+
canonicalJson({
|
|
15208
|
+
mode: right.mode,
|
|
15209
|
+
scopes: right.scopes,
|
|
15210
|
+
idempotencyKey: right.idempotencyKey,
|
|
15211
|
+
}));
|
|
15212
|
+
}
|
|
14749
15213
|
pendingLaunchMatches(left, right) {
|
|
14750
|
-
|
|
15214
|
+
const leftBound = pendingLaunchIsBound(left);
|
|
15215
|
+
const rightBound = pendingLaunchIsBound(right);
|
|
15216
|
+
if (leftBound !== rightBound)
|
|
15217
|
+
return false;
|
|
15218
|
+
if (leftBound && rightBound) {
|
|
15219
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
15220
|
+
}
|
|
15221
|
+
return this.pendingLaunchBaseMatches(left, right);
|
|
14751
15222
|
}
|
|
14752
15223
|
stagePendingLaunch(pendingLaunch, authority, sourceOwnerId) {
|
|
14753
|
-
|
|
15224
|
+
const boundPendingLaunch = this.bootstrap
|
|
15225
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15226
|
+
: null;
|
|
15227
|
+
if (!boundPendingLaunch ||
|
|
15228
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(sourceOwnerId) ||
|
|
14754
15229
|
sourceOwnerId === authority.ownerId) {
|
|
14755
15230
|
return false;
|
|
14756
15231
|
}
|
|
@@ -14761,35 +15236,82 @@ export default class AiLiveAssist {
|
|
|
14761
15236
|
if (authorityOrder < 0 ||
|
|
14762
15237
|
(authorityOrder === 0 &&
|
|
14763
15238
|
(existing?.sourceOwnerId !== sourceOwnerId ||
|
|
14764
|
-
!this.pendingLaunchMatches(existing.pendingLaunch
|
|
15239
|
+
!this.pendingLaunchMatches(!pendingLaunchIsBound(existing.pendingLaunch) && this.bootstrap
|
|
15240
|
+
? (bindPendingLaunchIntent(existing.pendingLaunch, this.bootstrap) ?? existing.pendingLaunch)
|
|
15241
|
+
: existing.pendingLaunch, boundPendingLaunch) ||
|
|
14765
15242
|
existing.state !== "prepared"))) {
|
|
14766
15243
|
return false;
|
|
14767
15244
|
}
|
|
14768
15245
|
return this.persistStagedPendingLaunch({
|
|
14769
|
-
v:
|
|
15246
|
+
v: 2,
|
|
14770
15247
|
sourceOwnerId,
|
|
14771
15248
|
authority: { ...authority },
|
|
14772
15249
|
state: "prepared",
|
|
14773
15250
|
claimAuthority: null,
|
|
14774
15251
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14775
15252
|
pendingLaunch: {
|
|
14776
|
-
...
|
|
14777
|
-
scopes: [...
|
|
15253
|
+
...boundPendingLaunch,
|
|
15254
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14778
15255
|
},
|
|
14779
15256
|
});
|
|
14780
15257
|
}
|
|
14781
15258
|
clearStagedPendingLaunch(pendingLaunch, authority) {
|
|
14782
15259
|
const staged = this.readStagedPendingLaunch();
|
|
15260
|
+
const comparablePending = this.bootstrap && !pendingLaunchIsBound(pendingLaunch)
|
|
15261
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15262
|
+
: pendingLaunch;
|
|
15263
|
+
const comparableStaged = this.bootstrap && staged && !pendingLaunchIsBound(staged.pendingLaunch)
|
|
15264
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15265
|
+
: staged?.pendingLaunch;
|
|
14783
15266
|
if (!staged ||
|
|
15267
|
+
!comparablePending ||
|
|
15268
|
+
!comparableStaged ||
|
|
14784
15269
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14785
|
-
!this.pendingLaunchMatches(
|
|
14786
|
-
return;
|
|
15270
|
+
!this.pendingLaunchMatches(comparableStaged, comparablePending)) {
|
|
15271
|
+
return false;
|
|
14787
15272
|
}
|
|
14788
15273
|
try {
|
|
14789
|
-
|
|
15274
|
+
const snapshots = [
|
|
15275
|
+
{
|
|
15276
|
+
key: this.stagedPendingLaunchStorageKey,
|
|
15277
|
+
raw: localStorage.getItem(this.stagedPendingLaunchStorageKey),
|
|
15278
|
+
version: 2,
|
|
15279
|
+
},
|
|
15280
|
+
{
|
|
15281
|
+
key: this.legacyStagedPendingLaunchStorageKey,
|
|
15282
|
+
raw: localStorage.getItem(this.legacyStagedPendingLaunchStorageKey),
|
|
15283
|
+
version: 1,
|
|
15284
|
+
},
|
|
15285
|
+
];
|
|
15286
|
+
const removable = [];
|
|
15287
|
+
for (const snapshot of snapshots) {
|
|
15288
|
+
if (snapshot.raw === null)
|
|
15289
|
+
continue;
|
|
15290
|
+
const shadow = parseStagedPendingLaunch(snapshot.raw);
|
|
15291
|
+
if (!shadow || shadow.v !== snapshot.version)
|
|
15292
|
+
return false;
|
|
15293
|
+
const authorityOrder = compareTransferAuthority(shadow.authority, staged.authority);
|
|
15294
|
+
if (authorityOrder > 0)
|
|
15295
|
+
continue;
|
|
15296
|
+
if (authorityOrder === 0 &&
|
|
15297
|
+
(shadow.sourceOwnerId !== staged.sourceOwnerId ||
|
|
15298
|
+
!this.pendingLaunchBaseMatches(shadow.pendingLaunch, staged.pendingLaunch))) {
|
|
15299
|
+
return false;
|
|
15300
|
+
}
|
|
15301
|
+
removable.push(snapshot);
|
|
15302
|
+
}
|
|
15303
|
+
if (removable.some((snapshot) => localStorage.getItem(snapshot.key) !== snapshot.raw)) {
|
|
15304
|
+
return false;
|
|
15305
|
+
}
|
|
15306
|
+
for (const snapshot of removable) {
|
|
15307
|
+
if (localStorage.getItem(snapshot.key) !== snapshot.raw)
|
|
15308
|
+
return false;
|
|
15309
|
+
localStorage.removeItem(snapshot.key);
|
|
15310
|
+
}
|
|
15311
|
+
return removable.every((snapshot) => localStorage.getItem(snapshot.key) === null);
|
|
14790
15312
|
}
|
|
14791
15313
|
catch {
|
|
14792
|
-
|
|
15314
|
+
return false;
|
|
14793
15315
|
}
|
|
14794
15316
|
}
|
|
14795
15317
|
clearOwnedStagedPendingLaunch() {
|
|
@@ -14806,7 +15328,7 @@ export default class AiLiveAssist {
|
|
|
14806
15328
|
const staged = this.readStagedPendingLaunch();
|
|
14807
15329
|
if (!pendingLaunch ||
|
|
14808
15330
|
!staged ||
|
|
14809
|
-
staged.pendingLaunch
|
|
15331
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch) ||
|
|
14810
15332
|
this.terminalEndIntent?.launchIdempotencyKey !==
|
|
14811
15333
|
pendingLaunch.idempotencyKey) {
|
|
14812
15334
|
return;
|
|
@@ -14817,11 +15339,15 @@ export default class AiLiveAssist {
|
|
|
14817
15339
|
const pendingLaunch = parseFocusedHandoffPayload(value)?.pendingLaunch;
|
|
14818
15340
|
if (!pendingLaunch)
|
|
14819
15341
|
return true;
|
|
15342
|
+
const boundPendingLaunch = this.bootstrap
|
|
15343
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15344
|
+
: null;
|
|
14820
15345
|
const staged = this.readStagedPendingLaunch();
|
|
14821
|
-
if (!
|
|
15346
|
+
if (!boundPendingLaunch ||
|
|
15347
|
+
!staged ||
|
|
14822
15348
|
staged.state !== "prepared" ||
|
|
14823
15349
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14824
|
-
!this.pendingLaunchMatches(staged.pendingLaunch,
|
|
15350
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, boundPendingLaunch)) {
|
|
14825
15351
|
return false;
|
|
14826
15352
|
}
|
|
14827
15353
|
return this.persistStagedPendingLaunch({
|
|
@@ -14865,6 +15391,12 @@ export default class AiLiveAssist {
|
|
|
14865
15391
|
}
|
|
14866
15392
|
markStagedPendingLaunchInFlight(pendingLaunch) {
|
|
14867
15393
|
const staged = this.readStagedPendingLaunch();
|
|
15394
|
+
const boundPendingLaunch = pendingLaunchIsBound(pendingLaunch)
|
|
15395
|
+
? pendingLaunch
|
|
15396
|
+
: this.bootstrap
|
|
15397
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15398
|
+
: null;
|
|
15399
|
+
const durablePendingLaunch = boundPendingLaunch ?? pendingLaunch;
|
|
14868
15400
|
// AiLiveAssist always installs a TabActivityLock with durable authority.
|
|
14869
15401
|
// Keep prototype-level test harnesses that intentionally omit the lock
|
|
14870
15402
|
// implementation isolated from the persistence contract.
|
|
@@ -14876,40 +15408,54 @@ export default class AiLiveAssist {
|
|
|
14876
15408
|
}
|
|
14877
15409
|
if (!staged) {
|
|
14878
15410
|
return this.persistStagedPendingLaunch({
|
|
14879
|
-
v: 1,
|
|
15411
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14880
15412
|
sourceOwnerId: randomId("launch_source"),
|
|
14881
15413
|
authority: { ...ownedAuthority },
|
|
14882
15414
|
state: "in_flight",
|
|
14883
15415
|
claimAuthority: { ...ownedAuthority },
|
|
14884
15416
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14885
15417
|
pendingLaunch: {
|
|
14886
|
-
...
|
|
14887
|
-
scopes: [...
|
|
15418
|
+
...durablePendingLaunch,
|
|
15419
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14888
15420
|
},
|
|
14889
15421
|
});
|
|
14890
15422
|
}
|
|
15423
|
+
const comparableStagedPendingLaunch = pendingLaunchIsBound(staged.pendingLaunch)
|
|
15424
|
+
? staged.pendingLaunch
|
|
15425
|
+
: this.bootstrap
|
|
15426
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15427
|
+
: null;
|
|
14891
15428
|
if (staged.pendingLaunch.idempotencyKey !== pendingLaunch.idempotencyKey ||
|
|
14892
15429
|
!staged.claimAuthority ||
|
|
14893
15430
|
compareTransferAuthority(staged.claimAuthority, ownedAuthority) !== 0 ||
|
|
14894
|
-
(staged.state !== "claimed" && staged.state !== "in_flight")
|
|
15431
|
+
(staged.state !== "claimed" && staged.state !== "in_flight") ||
|
|
15432
|
+
(boundPendingLaunch
|
|
15433
|
+
? !comparableStagedPendingLaunch ||
|
|
15434
|
+
!this.pendingLaunchMatches(comparableStagedPendingLaunch, boundPendingLaunch)
|
|
15435
|
+
: !this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch))) {
|
|
14895
15436
|
return false;
|
|
14896
15437
|
}
|
|
14897
|
-
if (staged.state === "in_flight"
|
|
15438
|
+
if (staged.state === "in_flight" &&
|
|
15439
|
+
staged.v === (boundPendingLaunch ? 2 : 1) &&
|
|
15440
|
+
this.pendingLaunchMatches(staged.pendingLaunch, durablePendingLaunch)) {
|
|
14898
15441
|
return true;
|
|
15442
|
+
}
|
|
14899
15443
|
return this.persistStagedPendingLaunch({
|
|
14900
15444
|
...staged,
|
|
15445
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14901
15446
|
state: "in_flight",
|
|
14902
15447
|
pendingLaunch: {
|
|
14903
|
-
...
|
|
14904
|
-
scopes: [...
|
|
15448
|
+
...durablePendingLaunch,
|
|
15449
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14905
15450
|
},
|
|
14906
15451
|
});
|
|
14907
15452
|
}
|
|
14908
15453
|
acceptStagedPendingLaunch(pendingLaunch, result) {
|
|
14909
15454
|
const staged = this.readStagedPendingLaunch();
|
|
14910
|
-
if (!staged
|
|
14911
|
-
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15455
|
+
if (!staged)
|
|
14912
15456
|
return true;
|
|
15457
|
+
if (!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15458
|
+
return false;
|
|
14913
15459
|
}
|
|
14914
15460
|
const ownedAuthority = this.lock.ownedAuthority?.() ?? null;
|
|
14915
15461
|
const stored = this.readResume();
|
|
@@ -14933,17 +15479,11 @@ export default class AiLiveAssist {
|
|
|
14933
15479
|
})) {
|
|
14934
15480
|
return false;
|
|
14935
15481
|
}
|
|
14936
|
-
|
|
14937
|
-
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14938
|
-
return localStorage.getItem(this.stagedPendingLaunchStorageKey) === null;
|
|
14939
|
-
}
|
|
14940
|
-
catch {
|
|
14941
|
-
return false;
|
|
14942
|
-
}
|
|
15482
|
+
return this.clearStagedPendingLaunch(staged.pendingLaunch, staged.authority);
|
|
14943
15483
|
}
|
|
14944
15484
|
hasStagedPendingLaunch(pendingLaunch) {
|
|
14945
|
-
|
|
14946
|
-
|
|
15485
|
+
const staged = this.readStagedPendingLaunch();
|
|
15486
|
+
return Boolean(staged && this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch));
|
|
14947
15487
|
}
|
|
14948
15488
|
storeTransferAuthority(authority) {
|
|
14949
15489
|
const current = this.readTransferAuthority();
|
|
@@ -15021,12 +15561,21 @@ export default class AiLiveAssist {
|
|
|
15021
15561
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15022
15562
|
return null;
|
|
15023
15563
|
try {
|
|
15024
|
-
const
|
|
15025
|
-
const
|
|
15026
|
-
|
|
15564
|
+
const v2Serialized = sessionStorage.getItem(this.freshAiReturnProvisionalStorageKey);
|
|
15565
|
+
const parsedV2 = parseFreshAiReturnProvisional(v2Serialized ? JSON.parse(v2Serialized) : null);
|
|
15566
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15567
|
+
if (v2Serialized && !v2) {
|
|
15027
15568
|
sessionStorage.removeItem(this.freshAiReturnProvisionalStorageKey);
|
|
15028
15569
|
}
|
|
15029
|
-
|
|
15570
|
+
if (v2)
|
|
15571
|
+
return v2;
|
|
15572
|
+
const legacySerialized = sessionStorage.getItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15573
|
+
const parsedLegacy = parseFreshAiReturnProvisional(legacySerialized ? JSON.parse(legacySerialized) : null);
|
|
15574
|
+
const legacy = parsedLegacy?.v === 1 ? parsedLegacy : null;
|
|
15575
|
+
if (legacySerialized && !legacy) {
|
|
15576
|
+
sessionStorage.removeItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15577
|
+
}
|
|
15578
|
+
return legacy;
|
|
15030
15579
|
}
|
|
15031
15580
|
catch {
|
|
15032
15581
|
return null;
|
|
@@ -15036,9 +15585,54 @@ export default class AiLiveAssist {
|
|
|
15036
15585
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15037
15586
|
return false;
|
|
15038
15587
|
try {
|
|
15039
|
-
|
|
15588
|
+
const serialized = provisional.v === 1
|
|
15589
|
+
? {
|
|
15590
|
+
v: 1,
|
|
15591
|
+
linkId: provisional.linkId,
|
|
15592
|
+
conversationDisplayId: provisional.conversationDisplayId,
|
|
15593
|
+
mode: provisional.mode,
|
|
15594
|
+
phase: provisional.phase,
|
|
15595
|
+
launchIdempotencyKey: provisional.launchIdempotencyKey,
|
|
15596
|
+
sessionId: provisional.sessionId,
|
|
15597
|
+
expiresAt: provisional.expiresAt,
|
|
15598
|
+
}
|
|
15599
|
+
: provisional;
|
|
15600
|
+
const storageKey = provisional.v === 2
|
|
15601
|
+
? this.freshAiReturnProvisionalStorageKey
|
|
15602
|
+
: this.legacyFreshAiReturnProvisionalStorageKey;
|
|
15603
|
+
sessionStorage.setItem(storageKey, JSON.stringify(serialized));
|
|
15604
|
+
const normalized = provisional.v === 1
|
|
15605
|
+
? {
|
|
15606
|
+
...provisional,
|
|
15607
|
+
protocolVersion: null,
|
|
15608
|
+
experienceMode: null,
|
|
15609
|
+
scopes: null,
|
|
15610
|
+
}
|
|
15611
|
+
: provisional;
|
|
15612
|
+
const directReadback = parseFreshAiReturnProvisional(JSON.parse(sessionStorage.getItem(storageKey) ?? "null"));
|
|
15613
|
+
if (canonicalJson(directReadback) !== canonicalJson(normalized)) {
|
|
15614
|
+
return false;
|
|
15615
|
+
}
|
|
15616
|
+
if (provisional.v === 2) {
|
|
15617
|
+
const legacySerialized = sessionStorage.getItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15618
|
+
const legacy = parseFreshAiReturnProvisional(legacySerialized ? JSON.parse(legacySerialized) : null);
|
|
15619
|
+
const sameReturnLineage = legacy?.v === 1 &&
|
|
15620
|
+
legacy.linkId === provisional.linkId &&
|
|
15621
|
+
legacy.conversationDisplayId === provisional.conversationDisplayId &&
|
|
15622
|
+
legacy.mode === provisional.mode &&
|
|
15623
|
+
legacy.expiresAt === provisional.expiresAt;
|
|
15624
|
+
if (sameReturnLineage &&
|
|
15625
|
+
(legacy.phase === "released" ||
|
|
15626
|
+
provisional.phase === "released" ||
|
|
15627
|
+
(legacy.phase === provisional.phase &&
|
|
15628
|
+
legacy.launchIdempotencyKey ===
|
|
15629
|
+
provisional.launchIdempotencyKey &&
|
|
15630
|
+
legacy.sessionId === provisional.sessionId))) {
|
|
15631
|
+
sessionStorage.removeItem(this.legacyFreshAiReturnProvisionalStorageKey);
|
|
15632
|
+
}
|
|
15633
|
+
}
|
|
15040
15634
|
return (canonicalJson(this.readFreshAiReturnProvisional()) ===
|
|
15041
|
-
canonicalJson(
|
|
15635
|
+
canonicalJson(normalized));
|
|
15042
15636
|
}
|
|
15043
15637
|
catch {
|
|
15044
15638
|
return false;
|
|
@@ -15048,19 +15642,21 @@ export default class AiLiveAssist {
|
|
|
15048
15642
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15049
15643
|
return;
|
|
15050
15644
|
try {
|
|
15051
|
-
const
|
|
15645
|
+
const storageKey = expected?.v === 1
|
|
15646
|
+
? this.legacyFreshAiReturnProvisionalStorageKey
|
|
15647
|
+
: this.freshAiReturnProvisionalStorageKey;
|
|
15648
|
+
const serialized = sessionStorage.getItem(storageKey);
|
|
15052
15649
|
if (!serialized)
|
|
15053
15650
|
return;
|
|
15054
15651
|
if (expected) {
|
|
15055
15652
|
const stored = parseFreshAiReturnProvisional(JSON.parse(serialized));
|
|
15056
15653
|
if (canonicalJson(stored) !== canonicalJson(expected))
|
|
15057
15654
|
return;
|
|
15058
|
-
if (sessionStorage.getItem(
|
|
15059
|
-
serialized) {
|
|
15655
|
+
if (sessionStorage.getItem(storageKey) !== serialized) {
|
|
15060
15656
|
return;
|
|
15061
15657
|
}
|
|
15062
15658
|
}
|
|
15063
|
-
sessionStorage.removeItem(
|
|
15659
|
+
sessionStorage.removeItem(storageKey);
|
|
15064
15660
|
}
|
|
15065
15661
|
catch {
|
|
15066
15662
|
/* noop */
|
|
@@ -15651,10 +16247,7 @@ export default class AiLiveAssist {
|
|
|
15651
16247
|
!this.view ||
|
|
15652
16248
|
(record &&
|
|
15653
16249
|
(!record.tabId ||
|
|
15654
|
-
record.
|
|
15655
|
-
resolveAiGuideProtocolVersion(this.bootstrap) ||
|
|
15656
|
-
record.experienceMode !==
|
|
15657
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16250
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15658
16251
|
(pendingLaunch &&
|
|
15659
16252
|
(this.visitorActiveTakeoverOnly ||
|
|
15660
16253
|
((this.bootstrap.experienceMode ?? "standard") === "voice_presence" &&
|
|
@@ -15690,10 +16283,11 @@ export default class AiLiveAssist {
|
|
|
15690
16283
|
this.lock.release();
|
|
15691
16284
|
};
|
|
15692
16285
|
const preparedMatches = record
|
|
15693
|
-
? prepared?.resume
|
|
16286
|
+
? Boolean(prepared?.resume &&
|
|
16287
|
+
this.sameResumeAuthorityIdentity(prepared.resume, record))
|
|
15694
16288
|
: Boolean(pendingLaunch &&
|
|
15695
|
-
prepared?.pendingLaunch
|
|
15696
|
-
|
|
16289
|
+
prepared?.pendingLaunch &&
|
|
16290
|
+
this.pendingLaunchMatches(prepared.pendingLaunch, pendingLaunch));
|
|
15697
16291
|
if (!payload ||
|
|
15698
16292
|
(!record && !pendingLaunch) ||
|
|
15699
16293
|
!this.bootstrap ||
|
|
@@ -15706,10 +16300,7 @@ export default class AiLiveAssist {
|
|
|
15706
16300
|
!preparedMatches ||
|
|
15707
16301
|
(record &&
|
|
15708
16302
|
(!record.tabId ||
|
|
15709
|
-
record.
|
|
15710
|
-
resolveAiGuideProtocolVersion(this.bootstrap) ||
|
|
15711
|
-
record.experienceMode !==
|
|
15712
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16303
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15713
16304
|
(pendingLaunch && this.visitorActiveTakeoverOnly)) {
|
|
15714
16305
|
abandonUnusableCommit();
|
|
15715
16306
|
return;
|
|
@@ -15721,7 +16312,7 @@ export default class AiLiveAssist {
|
|
|
15721
16312
|
return;
|
|
15722
16313
|
}
|
|
15723
16314
|
if (this.lastLaunch &&
|
|
15724
|
-
this.lastLaunch
|
|
16315
|
+
!this.pendingLaunchMatches(this.lastLaunch, stagedPending)) {
|
|
15725
16316
|
this.clearStagedPendingLaunch(stagedPending, authority);
|
|
15726
16317
|
}
|
|
15727
16318
|
else {
|
|
@@ -15733,6 +16324,12 @@ export default class AiLiveAssist {
|
|
|
15733
16324
|
return;
|
|
15734
16325
|
}
|
|
15735
16326
|
const storedResume = record ? this.readResume() : null;
|
|
16327
|
+
if (record &&
|
|
16328
|
+
storedResume?.sessionId === record.sessionId &&
|
|
16329
|
+
!this.sameResumeAuthorityIdentity(record, storedResume)) {
|
|
16330
|
+
abandonUnusableCommit();
|
|
16331
|
+
return;
|
|
16332
|
+
}
|
|
15736
16333
|
const effectivePayload = record &&
|
|
15737
16334
|
storedResume?.sessionId === record.sessionId &&
|
|
15738
16335
|
storedResume.resumeCredential !== record.resumeCredential
|
|
@@ -15773,11 +16370,11 @@ export default class AiLiveAssist {
|
|
|
15773
16370
|
completeFocusedTabRelinquish(value) {
|
|
15774
16371
|
const payload = parseFocusedHandoffPayload(value);
|
|
15775
16372
|
const sameResume = Boolean(payload?.resume &&
|
|
15776
|
-
this.focusedHandoffInFlight?.resume
|
|
15777
|
-
|
|
16373
|
+
this.focusedHandoffInFlight?.resume &&
|
|
16374
|
+
this.sameResumeAuthorityIdentity(this.focusedHandoffInFlight.resume, payload.resume));
|
|
15778
16375
|
const samePendingLaunch = Boolean(payload?.pendingLaunch &&
|
|
15779
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15780
|
-
|
|
16376
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16377
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, payload.pendingLaunch));
|
|
15781
16378
|
if (!payload || (!sameResume && !samePendingLaunch)) {
|
|
15782
16379
|
return;
|
|
15783
16380
|
}
|
|
@@ -15808,8 +16405,8 @@ export default class AiLiveAssist {
|
|
|
15808
16405
|
this.clearStagedPendingLaunch(pendingLaunch, authority);
|
|
15809
16406
|
}
|
|
15810
16407
|
if (pendingLaunch &&
|
|
15811
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15812
|
-
|
|
16408
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16409
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, pendingLaunch)) {
|
|
15813
16410
|
this.focusedHandoffInFlight = null;
|
|
15814
16411
|
if (this.terminalRequestPending ||
|
|
15815
16412
|
this.ended ||
|
|
@@ -15831,7 +16428,7 @@ export default class AiLiveAssist {
|
|
|
15831
16428
|
queueMicrotask(() => {
|
|
15832
16429
|
const retry = this.lastLaunch;
|
|
15833
16430
|
if (!retry ||
|
|
15834
|
-
retry
|
|
16431
|
+
!this.pendingLaunchMatches(retry, pendingLaunch) ||
|
|
15835
16432
|
this.terminalRequestPending ||
|
|
15836
16433
|
this.ended ||
|
|
15837
16434
|
this.destroyed ||
|
|
@@ -15840,7 +16437,9 @@ export default class AiLiveAssist {
|
|
|
15840
16437
|
return;
|
|
15841
16438
|
}
|
|
15842
16439
|
this.lastLaunch = null;
|
|
15843
|
-
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey
|
|
16440
|
+
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey, {
|
|
16441
|
+
pendingLaunch: retry,
|
|
16442
|
+
});
|
|
15844
16443
|
});
|
|
15845
16444
|
return;
|
|
15846
16445
|
}
|
|
@@ -15906,7 +16505,7 @@ export default class AiLiveAssist {
|
|
|
15906
16505
|
this.view &&
|
|
15907
16506
|
!this.visitorActiveTakeoverOnly) {
|
|
15908
16507
|
this.lastLaunch = null;
|
|
15909
|
-
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey);
|
|
16508
|
+
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey, { pendingLaunch: deferredLaunch });
|
|
15910
16509
|
return;
|
|
15911
16510
|
}
|
|
15912
16511
|
this.lock.release();
|