@oxvo/ai-live-assist 7.4.36 → 7.4.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/AiLiveAssist.d.ts +10 -1
- package/cjs/AiLiveAssist.js +797 -194
- package/cjs/context.d.ts +2 -0
- package/cjs/context.js +213 -22
- package/cjs/index.js +2 -2
- package/cjs/media.js +3 -2
- package/cjs/preview.d.ts +1 -1
- package/cjs/preview.js +2 -1
- package/cjs/types.d.ts +1 -0
- package/cjs/ui.d.ts +9 -0
- package/cjs/ui.js +478 -141
- package/cjs/version.d.ts +1 -1
- package/cjs/version.js +1 -1
- package/lib/AiLiveAssist.d.ts +10 -1
- package/lib/AiLiveAssist.js +797 -194
- package/lib/context.d.ts +2 -0
- package/lib/context.js +213 -22
- package/lib/index.js +2 -2
- package/lib/media.js +3 -2
- package/lib/preview.d.ts +1 -1
- package/lib/preview.js +2 -1
- package/lib/types.d.ts +1 -0
- package/lib/ui.d.ts +9 -0
- package/lib/ui.js +478 -141
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/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") ||
|
|
@@ -7350,6 +7640,7 @@ export default class AiLiveAssist {
|
|
|
7350
7640
|
? null
|
|
7351
7641
|
: (result.assistant?.welcomeMessage ??
|
|
7352
7642
|
this.bootstrap?.appearance?.welcomeMessage);
|
|
7643
|
+
let renderedPendingTextQuestion = false;
|
|
7353
7644
|
if (mode === "text" &&
|
|
7354
7645
|
this.pendingQuestion &&
|
|
7355
7646
|
!this.pendingQuestion.shown) {
|
|
@@ -7357,8 +7648,11 @@ export default class AiLiveAssist {
|
|
|
7357
7648
|
this.pendingQuestion.shown = true;
|
|
7358
7649
|
this.rememberTranscript(this.pendingQuestion.submissionId, "user", this.pendingQuestion.text);
|
|
7359
7650
|
(this.renderedTranscriptIds ?? (this.renderedTranscriptIds = new Set())).add(this.pendingQuestion.submissionId);
|
|
7651
|
+
renderedPendingTextQuestion = true;
|
|
7360
7652
|
}
|
|
7361
|
-
const visibleWelcome =
|
|
7653
|
+
const visibleWelcome = resume ||
|
|
7654
|
+
renderedPendingTextQuestion ||
|
|
7655
|
+
this.transcriptRestoredFromCache ||
|
|
7362
7656
|
(this.view instanceof VoicePresenceView && mode === "voice")
|
|
7363
7657
|
? null
|
|
7364
7658
|
: welcomeMessage;
|
|
@@ -13093,7 +13387,7 @@ export default class AiLiveAssist {
|
|
|
13093
13387
|
return;
|
|
13094
13388
|
}
|
|
13095
13389
|
this.messengerReturnRetryRemaining -= 1;
|
|
13096
|
-
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 });
|
|
13097
13391
|
return retryAttempt.finally(() => {
|
|
13098
13392
|
if (this.result && !this.ended) {
|
|
13099
13393
|
this.messengerReturnRetryRemaining = null;
|
|
@@ -13113,7 +13407,7 @@ export default class AiLiveAssist {
|
|
|
13113
13407
|
return this.resume(resume);
|
|
13114
13408
|
}
|
|
13115
13409
|
if (this.lastLaunch) {
|
|
13116
|
-
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 });
|
|
13117
13411
|
}
|
|
13118
13412
|
}
|
|
13119
13413
|
scheduleReconnect(force = false) {
|
|
@@ -13443,6 +13737,18 @@ export default class AiLiveAssist {
|
|
|
13443
13737
|
canonicalJson(left.messengerBinding) ===
|
|
13444
13738
|
canonicalJson(right.messengerBinding));
|
|
13445
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
|
+
}
|
|
13446
13752
|
sameModeUpgradeIntent(left, right) {
|
|
13447
13753
|
return (left?.requestId === right?.requestId &&
|
|
13448
13754
|
left?.resumeIdempotencyKey === right?.resumeIdempotencyKey &&
|
|
@@ -13811,10 +14117,22 @@ export default class AiLiveAssist {
|
|
|
13811
14117
|
this.state("ended");
|
|
13812
14118
|
}
|
|
13813
14119
|
captureTerminalAuthorizedResult(result, sourceRecord = null) {
|
|
13814
|
-
|
|
13815
|
-
(
|
|
13816
|
-
|
|
13817
|
-
|
|
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))) {
|
|
13818
14136
|
return false;
|
|
13819
14137
|
}
|
|
13820
14138
|
const currentIntent = this.terminalEndIntent;
|
|
@@ -13840,6 +14158,22 @@ export default class AiLiveAssist {
|
|
|
13840
14158
|
}
|
|
13841
14159
|
return true;
|
|
13842
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
|
+
}
|
|
13843
14177
|
completeRuntimeEnd() {
|
|
13844
14178
|
if (this.ended)
|
|
13845
14179
|
return Promise.resolve();
|
|
@@ -13870,8 +14204,9 @@ export default class AiLiveAssist {
|
|
|
13870
14204
|
if (terminalAuthorization) {
|
|
13871
14205
|
return this.waitForTerminalRequestDeadline(terminalAuthorization, this.terminalSessionAuthorizationController).then((authorized) => {
|
|
13872
14206
|
const result = authorized ?? this.result ?? this.modeUpgradeTerminalResult;
|
|
13873
|
-
if (result
|
|
13874
|
-
this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)
|
|
14207
|
+
if (result &&
|
|
14208
|
+
!this.captureTerminalAuthorizedResult(result, this.terminalResumeRecord)) {
|
|
14209
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
13875
14210
|
}
|
|
13876
14211
|
return this.finishRuntimeEndAttempt(result);
|
|
13877
14212
|
}, (error) => {
|
|
@@ -13888,7 +14223,9 @@ export default class AiLiveAssist {
|
|
|
13888
14223
|
}
|
|
13889
14224
|
if (this.terminalPendingLaunch) {
|
|
13890
14225
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13891
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14226
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14227
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14228
|
+
}
|
|
13892
14229
|
return this.finishRuntimeEndAttempt(result);
|
|
13893
14230
|
}, (recoveryError) => this.retryTerminalEnd(recoveryError));
|
|
13894
14231
|
}
|
|
@@ -13915,7 +14252,9 @@ export default class AiLiveAssist {
|
|
|
13915
14252
|
return this.finishRuntimeEndAttempt(null);
|
|
13916
14253
|
if (this.terminalPendingLaunch) {
|
|
13917
14254
|
return this.requestTerminalPendingLaunch(this.terminalPendingLaunch).then((result) => {
|
|
13918
|
-
this.captureTerminalAuthorizedResult(result)
|
|
14255
|
+
if (!this.captureTerminalAuthorizedResult(result)) {
|
|
14256
|
+
return this.retryRejectedTerminalAuthorizedResult(result);
|
|
14257
|
+
}
|
|
13919
14258
|
return this.finishRuntimeEndAttempt(result);
|
|
13920
14259
|
}, (error) => this.retryTerminalEnd(error));
|
|
13921
14260
|
}
|
|
@@ -14072,13 +14411,21 @@ export default class AiLiveAssist {
|
|
|
14072
14411
|
requestTerminalPendingLaunch(pendingLaunch) {
|
|
14073
14412
|
const intent = this.terminalEndIntent;
|
|
14074
14413
|
if (!intent ||
|
|
14075
|
-
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey
|
|
14076
|
-
!this.markStagedPendingLaunchInFlight(pendingLaunch)) {
|
|
14414
|
+
intent.launchIdempotencyKey !== pendingLaunch.idempotencyKey) {
|
|
14077
14415
|
return Promise.reject(new TerminalRequestDeferredError());
|
|
14078
14416
|
}
|
|
14079
14417
|
const controller = new AbortController();
|
|
14080
14418
|
const request = (async () => {
|
|
14081
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
|
+
};
|
|
14082
14429
|
const replay = await this.replayReadiness.wait();
|
|
14083
14430
|
if (this.destroyed ||
|
|
14084
14431
|
this.ended ||
|
|
@@ -14087,28 +14434,34 @@ export default class AiLiveAssist {
|
|
|
14087
14434
|
throw new TerminalRequestDeferredError();
|
|
14088
14435
|
}
|
|
14089
14436
|
this.replayReadiness.assertCurrent(replay);
|
|
14090
|
-
|
|
14437
|
+
const result = await this.runtime.launch({
|
|
14091
14438
|
siteKey: this.siteKey,
|
|
14092
14439
|
configRevision: bootstrap.configRevision,
|
|
14093
14440
|
path: path(),
|
|
14094
14441
|
tabId: this.tabId,
|
|
14095
|
-
mode:
|
|
14442
|
+
mode: boundPendingLaunch.mode,
|
|
14096
14443
|
anonymousId: this.visitorId(),
|
|
14097
14444
|
replay,
|
|
14098
14445
|
disclosureChecksum: bootstrap.consent.disclosureChecksum,
|
|
14099
|
-
scopes:
|
|
14446
|
+
scopes: boundPendingLaunch.scopes,
|
|
14100
14447
|
locale: this.locale,
|
|
14101
|
-
microphone:
|
|
14448
|
+
microphone: boundPendingLaunch.mode === "voice",
|
|
14102
14449
|
reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)")
|
|
14103
14450
|
.matches,
|
|
14104
|
-
protocolVersion:
|
|
14105
|
-
experienceMode:
|
|
14451
|
+
protocolVersion: boundPendingLaunch.protocolVersion,
|
|
14452
|
+
experienceMode: boundPendingLaunch.experienceMode,
|
|
14106
14453
|
...(this.activeFallback
|
|
14107
14454
|
? { fallbackIntent: this.activeFallback }
|
|
14108
14455
|
: {}),
|
|
14109
14456
|
lowPower: this.lowPowerMode(),
|
|
14110
|
-
idempotencyKey:
|
|
14457
|
+
idempotencyKey: boundPendingLaunch.idempotencyKey,
|
|
14111
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;
|
|
14112
14465
|
})();
|
|
14113
14466
|
return this.waitForTerminalRequestDeadline(request, controller);
|
|
14114
14467
|
}
|
|
@@ -14272,8 +14625,7 @@ export default class AiLiveAssist {
|
|
|
14272
14625
|
const retainTerminalLaunch = Boolean(this.terminalSessionAuthorization &&
|
|
14273
14626
|
this.terminalPendingLaunch &&
|
|
14274
14627
|
staged?.state === "in_flight" &&
|
|
14275
|
-
staged.pendingLaunch.
|
|
14276
|
-
this.terminalPendingLaunch.idempotencyKey);
|
|
14628
|
+
this.pendingLaunchMatches(staged.pendingLaunch, this.terminalPendingLaunch));
|
|
14277
14629
|
if (!retainTerminalLaunch)
|
|
14278
14630
|
this.clearOwnedStagedPendingLaunch();
|
|
14279
14631
|
this.lock?.cancelPendingHandoff?.();
|
|
@@ -14697,15 +15049,18 @@ export default class AiLiveAssist {
|
|
|
14697
15049
|
focusedHandoffPayload() {
|
|
14698
15050
|
this.transcript ?? (this.transcript = []);
|
|
14699
15051
|
const resume = this.readResume();
|
|
15052
|
+
const pendingLaunch = !resume && this.lastLaunch && this.bootstrap
|
|
15053
|
+
? bindPendingLaunchIntent(this.lastLaunch, this.bootstrap)
|
|
15054
|
+
: null;
|
|
14700
15055
|
return {
|
|
14701
|
-
v: 1,
|
|
15056
|
+
v: pendingLaunch ? 2 : 1,
|
|
14702
15057
|
resume,
|
|
14703
15058
|
transcript: this.transcript.map((entry) => ({ ...entry })),
|
|
14704
|
-
...(
|
|
15059
|
+
...(pendingLaunch
|
|
14705
15060
|
? {
|
|
14706
15061
|
pendingLaunch: {
|
|
14707
|
-
...
|
|
14708
|
-
scopes: [...
|
|
15062
|
+
...pendingLaunch,
|
|
15063
|
+
scopes: [...pendingLaunch.scopes],
|
|
14709
15064
|
},
|
|
14710
15065
|
}
|
|
14711
15066
|
: {}),
|
|
@@ -14720,33 +15075,157 @@ export default class AiLiveAssist {
|
|
|
14720
15075
|
}
|
|
14721
15076
|
}
|
|
14722
15077
|
readStagedPendingLaunch() {
|
|
14723
|
-
let raw = null;
|
|
14724
15078
|
try {
|
|
14725
|
-
|
|
14726
|
-
const
|
|
14727
|
-
|
|
15079
|
+
const v2Raw = localStorage.getItem(this.stagedPendingLaunchStorageKey);
|
|
15080
|
+
const parsedV2 = parseStagedPendingLaunch(v2Raw);
|
|
15081
|
+
const v2 = parsedV2?.v === 2 ? parsedV2 : null;
|
|
15082
|
+
if (v2Raw && !v2) {
|
|
14728
15083
|
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14729
15084
|
}
|
|
14730
|
-
|
|
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;
|
|
14731
15106
|
}
|
|
14732
15107
|
catch {
|
|
14733
15108
|
return null;
|
|
14734
15109
|
}
|
|
14735
15110
|
}
|
|
14736
15111
|
persistStagedPendingLaunch(staged) {
|
|
15112
|
+
let rollback = null;
|
|
14737
15113
|
try {
|
|
14738
|
-
|
|
14739
|
-
|
|
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;
|
|
14740
15189
|
}
|
|
14741
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
|
+
}
|
|
14742
15198
|
return false;
|
|
14743
15199
|
}
|
|
14744
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
|
+
}
|
|
14745
15213
|
pendingLaunchMatches(left, right) {
|
|
14746
|
-
|
|
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);
|
|
14747
15222
|
}
|
|
14748
15223
|
stagePendingLaunch(pendingLaunch, authority, sourceOwnerId) {
|
|
14749
|
-
|
|
15224
|
+
const boundPendingLaunch = this.bootstrap
|
|
15225
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15226
|
+
: null;
|
|
15227
|
+
if (!boundPendingLaunch ||
|
|
15228
|
+
!PUBLIC_REQUEST_ID_PATTERN.test(sourceOwnerId) ||
|
|
14750
15229
|
sourceOwnerId === authority.ownerId) {
|
|
14751
15230
|
return false;
|
|
14752
15231
|
}
|
|
@@ -14757,35 +15236,82 @@ export default class AiLiveAssist {
|
|
|
14757
15236
|
if (authorityOrder < 0 ||
|
|
14758
15237
|
(authorityOrder === 0 &&
|
|
14759
15238
|
(existing?.sourceOwnerId !== sourceOwnerId ||
|
|
14760
|
-
!this.pendingLaunchMatches(existing.pendingLaunch
|
|
15239
|
+
!this.pendingLaunchMatches(!pendingLaunchIsBound(existing.pendingLaunch) && this.bootstrap
|
|
15240
|
+
? (bindPendingLaunchIntent(existing.pendingLaunch, this.bootstrap) ?? existing.pendingLaunch)
|
|
15241
|
+
: existing.pendingLaunch, boundPendingLaunch) ||
|
|
14761
15242
|
existing.state !== "prepared"))) {
|
|
14762
15243
|
return false;
|
|
14763
15244
|
}
|
|
14764
15245
|
return this.persistStagedPendingLaunch({
|
|
14765
|
-
v:
|
|
15246
|
+
v: 2,
|
|
14766
15247
|
sourceOwnerId,
|
|
14767
15248
|
authority: { ...authority },
|
|
14768
15249
|
state: "prepared",
|
|
14769
15250
|
claimAuthority: null,
|
|
14770
15251
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14771
15252
|
pendingLaunch: {
|
|
14772
|
-
...
|
|
14773
|
-
scopes: [...
|
|
15253
|
+
...boundPendingLaunch,
|
|
15254
|
+
scopes: [...boundPendingLaunch.scopes],
|
|
14774
15255
|
},
|
|
14775
15256
|
});
|
|
14776
15257
|
}
|
|
14777
15258
|
clearStagedPendingLaunch(pendingLaunch, authority) {
|
|
14778
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;
|
|
14779
15266
|
if (!staged ||
|
|
15267
|
+
!comparablePending ||
|
|
15268
|
+
!comparableStaged ||
|
|
14780
15269
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14781
|
-
!this.pendingLaunchMatches(
|
|
14782
|
-
return;
|
|
15270
|
+
!this.pendingLaunchMatches(comparableStaged, comparablePending)) {
|
|
15271
|
+
return false;
|
|
14783
15272
|
}
|
|
14784
15273
|
try {
|
|
14785
|
-
|
|
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);
|
|
14786
15312
|
}
|
|
14787
15313
|
catch {
|
|
14788
|
-
|
|
15314
|
+
return false;
|
|
14789
15315
|
}
|
|
14790
15316
|
}
|
|
14791
15317
|
clearOwnedStagedPendingLaunch() {
|
|
@@ -14802,7 +15328,7 @@ export default class AiLiveAssist {
|
|
|
14802
15328
|
const staged = this.readStagedPendingLaunch();
|
|
14803
15329
|
if (!pendingLaunch ||
|
|
14804
15330
|
!staged ||
|
|
14805
|
-
staged.pendingLaunch
|
|
15331
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch) ||
|
|
14806
15332
|
this.terminalEndIntent?.launchIdempotencyKey !==
|
|
14807
15333
|
pendingLaunch.idempotencyKey) {
|
|
14808
15334
|
return;
|
|
@@ -14813,11 +15339,15 @@ export default class AiLiveAssist {
|
|
|
14813
15339
|
const pendingLaunch = parseFocusedHandoffPayload(value)?.pendingLaunch;
|
|
14814
15340
|
if (!pendingLaunch)
|
|
14815
15341
|
return true;
|
|
15342
|
+
const boundPendingLaunch = this.bootstrap
|
|
15343
|
+
? bindPendingLaunchIntent(pendingLaunch, this.bootstrap)
|
|
15344
|
+
: null;
|
|
14816
15345
|
const staged = this.readStagedPendingLaunch();
|
|
14817
|
-
if (!
|
|
15346
|
+
if (!boundPendingLaunch ||
|
|
15347
|
+
!staged ||
|
|
14818
15348
|
staged.state !== "prepared" ||
|
|
14819
15349
|
compareTransferAuthority(staged.authority, authority) !== 0 ||
|
|
14820
|
-
!this.pendingLaunchMatches(staged.pendingLaunch,
|
|
15350
|
+
!this.pendingLaunchMatches(staged.pendingLaunch, boundPendingLaunch)) {
|
|
14821
15351
|
return false;
|
|
14822
15352
|
}
|
|
14823
15353
|
return this.persistStagedPendingLaunch({
|
|
@@ -14861,6 +15391,12 @@ export default class AiLiveAssist {
|
|
|
14861
15391
|
}
|
|
14862
15392
|
markStagedPendingLaunchInFlight(pendingLaunch) {
|
|
14863
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;
|
|
14864
15400
|
// AiLiveAssist always installs a TabActivityLock with durable authority.
|
|
14865
15401
|
// Keep prototype-level test harnesses that intentionally omit the lock
|
|
14866
15402
|
// implementation isolated from the persistence contract.
|
|
@@ -14872,40 +15408,54 @@ export default class AiLiveAssist {
|
|
|
14872
15408
|
}
|
|
14873
15409
|
if (!staged) {
|
|
14874
15410
|
return this.persistStagedPendingLaunch({
|
|
14875
|
-
v: 1,
|
|
15411
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14876
15412
|
sourceOwnerId: randomId("launch_source"),
|
|
14877
15413
|
authority: { ...ownedAuthority },
|
|
14878
15414
|
state: "in_flight",
|
|
14879
15415
|
claimAuthority: { ...ownedAuthority },
|
|
14880
15416
|
expiresAt: Date.now() + STAGED_PENDING_LAUNCH_TTL_MS,
|
|
14881
15417
|
pendingLaunch: {
|
|
14882
|
-
...
|
|
14883
|
-
scopes: [...
|
|
15418
|
+
...durablePendingLaunch,
|
|
15419
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14884
15420
|
},
|
|
14885
15421
|
});
|
|
14886
15422
|
}
|
|
15423
|
+
const comparableStagedPendingLaunch = pendingLaunchIsBound(staged.pendingLaunch)
|
|
15424
|
+
? staged.pendingLaunch
|
|
15425
|
+
: this.bootstrap
|
|
15426
|
+
? bindPendingLaunchIntent(staged.pendingLaunch, this.bootstrap)
|
|
15427
|
+
: null;
|
|
14887
15428
|
if (staged.pendingLaunch.idempotencyKey !== pendingLaunch.idempotencyKey ||
|
|
14888
15429
|
!staged.claimAuthority ||
|
|
14889
15430
|
compareTransferAuthority(staged.claimAuthority, ownedAuthority) !== 0 ||
|
|
14890
|
-
(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))) {
|
|
14891
15436
|
return false;
|
|
14892
15437
|
}
|
|
14893
|
-
if (staged.state === "in_flight"
|
|
15438
|
+
if (staged.state === "in_flight" &&
|
|
15439
|
+
staged.v === (boundPendingLaunch ? 2 : 1) &&
|
|
15440
|
+
this.pendingLaunchMatches(staged.pendingLaunch, durablePendingLaunch)) {
|
|
14894
15441
|
return true;
|
|
15442
|
+
}
|
|
14895
15443
|
return this.persistStagedPendingLaunch({
|
|
14896
15444
|
...staged,
|
|
15445
|
+
v: boundPendingLaunch ? 2 : 1,
|
|
14897
15446
|
state: "in_flight",
|
|
14898
15447
|
pendingLaunch: {
|
|
14899
|
-
...
|
|
14900
|
-
scopes: [...
|
|
15448
|
+
...durablePendingLaunch,
|
|
15449
|
+
scopes: [...durablePendingLaunch.scopes],
|
|
14901
15450
|
},
|
|
14902
15451
|
});
|
|
14903
15452
|
}
|
|
14904
15453
|
acceptStagedPendingLaunch(pendingLaunch, result) {
|
|
14905
15454
|
const staged = this.readStagedPendingLaunch();
|
|
14906
|
-
if (!staged
|
|
14907
|
-
!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15455
|
+
if (!staged)
|
|
14908
15456
|
return true;
|
|
15457
|
+
if (!this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch)) {
|
|
15458
|
+
return false;
|
|
14909
15459
|
}
|
|
14910
15460
|
const ownedAuthority = this.lock.ownedAuthority?.() ?? null;
|
|
14911
15461
|
const stored = this.readResume();
|
|
@@ -14929,17 +15479,11 @@ export default class AiLiveAssist {
|
|
|
14929
15479
|
})) {
|
|
14930
15480
|
return false;
|
|
14931
15481
|
}
|
|
14932
|
-
|
|
14933
|
-
localStorage.removeItem(this.stagedPendingLaunchStorageKey);
|
|
14934
|
-
return localStorage.getItem(this.stagedPendingLaunchStorageKey) === null;
|
|
14935
|
-
}
|
|
14936
|
-
catch {
|
|
14937
|
-
return false;
|
|
14938
|
-
}
|
|
15482
|
+
return this.clearStagedPendingLaunch(staged.pendingLaunch, staged.authority);
|
|
14939
15483
|
}
|
|
14940
15484
|
hasStagedPendingLaunch(pendingLaunch) {
|
|
14941
|
-
|
|
14942
|
-
|
|
15485
|
+
const staged = this.readStagedPendingLaunch();
|
|
15486
|
+
return Boolean(staged && this.pendingLaunchMatches(staged.pendingLaunch, pendingLaunch));
|
|
14943
15487
|
}
|
|
14944
15488
|
storeTransferAuthority(authority) {
|
|
14945
15489
|
const current = this.readTransferAuthority();
|
|
@@ -15017,12 +15561,21 @@ export default class AiLiveAssist {
|
|
|
15017
15561
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15018
15562
|
return null;
|
|
15019
15563
|
try {
|
|
15020
|
-
const
|
|
15021
|
-
const
|
|
15022
|
-
|
|
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) {
|
|
15023
15568
|
sessionStorage.removeItem(this.freshAiReturnProvisionalStorageKey);
|
|
15024
15569
|
}
|
|
15025
|
-
|
|
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;
|
|
15026
15579
|
}
|
|
15027
15580
|
catch {
|
|
15028
15581
|
return null;
|
|
@@ -15032,9 +15585,54 @@ export default class AiLiveAssist {
|
|
|
15032
15585
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15033
15586
|
return false;
|
|
15034
15587
|
try {
|
|
15035
|
-
|
|
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
|
+
}
|
|
15036
15634
|
return (canonicalJson(this.readFreshAiReturnProvisional()) ===
|
|
15037
|
-
canonicalJson(
|
|
15635
|
+
canonicalJson(normalized));
|
|
15038
15636
|
}
|
|
15039
15637
|
catch {
|
|
15040
15638
|
return false;
|
|
@@ -15044,19 +15642,21 @@ export default class AiLiveAssist {
|
|
|
15044
15642
|
if (!this.freshAiReturnProvisionalStorageKey)
|
|
15045
15643
|
return;
|
|
15046
15644
|
try {
|
|
15047
|
-
const
|
|
15645
|
+
const storageKey = expected?.v === 1
|
|
15646
|
+
? this.legacyFreshAiReturnProvisionalStorageKey
|
|
15647
|
+
: this.freshAiReturnProvisionalStorageKey;
|
|
15648
|
+
const serialized = sessionStorage.getItem(storageKey);
|
|
15048
15649
|
if (!serialized)
|
|
15049
15650
|
return;
|
|
15050
15651
|
if (expected) {
|
|
15051
15652
|
const stored = parseFreshAiReturnProvisional(JSON.parse(serialized));
|
|
15052
15653
|
if (canonicalJson(stored) !== canonicalJson(expected))
|
|
15053
15654
|
return;
|
|
15054
|
-
if (sessionStorage.getItem(
|
|
15055
|
-
serialized) {
|
|
15655
|
+
if (sessionStorage.getItem(storageKey) !== serialized) {
|
|
15056
15656
|
return;
|
|
15057
15657
|
}
|
|
15058
15658
|
}
|
|
15059
|
-
sessionStorage.removeItem(
|
|
15659
|
+
sessionStorage.removeItem(storageKey);
|
|
15060
15660
|
}
|
|
15061
15661
|
catch {
|
|
15062
15662
|
/* noop */
|
|
@@ -15647,10 +16247,7 @@ export default class AiLiveAssist {
|
|
|
15647
16247
|
!this.view ||
|
|
15648
16248
|
(record &&
|
|
15649
16249
|
(!record.tabId ||
|
|
15650
|
-
record.
|
|
15651
|
-
resolveAiGuideProtocolVersion(this.bootstrap) ||
|
|
15652
|
-
record.experienceMode !==
|
|
15653
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16250
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15654
16251
|
(pendingLaunch &&
|
|
15655
16252
|
(this.visitorActiveTakeoverOnly ||
|
|
15656
16253
|
((this.bootstrap.experienceMode ?? "standard") === "voice_presence" &&
|
|
@@ -15686,10 +16283,11 @@ export default class AiLiveAssist {
|
|
|
15686
16283
|
this.lock.release();
|
|
15687
16284
|
};
|
|
15688
16285
|
const preparedMatches = record
|
|
15689
|
-
? prepared?.resume
|
|
16286
|
+
? Boolean(prepared?.resume &&
|
|
16287
|
+
this.sameResumeAuthorityIdentity(prepared.resume, record))
|
|
15690
16288
|
: Boolean(pendingLaunch &&
|
|
15691
|
-
prepared?.pendingLaunch
|
|
15692
|
-
|
|
16289
|
+
prepared?.pendingLaunch &&
|
|
16290
|
+
this.pendingLaunchMatches(prepared.pendingLaunch, pendingLaunch));
|
|
15693
16291
|
if (!payload ||
|
|
15694
16292
|
(!record && !pendingLaunch) ||
|
|
15695
16293
|
!this.bootstrap ||
|
|
@@ -15702,10 +16300,7 @@ export default class AiLiveAssist {
|
|
|
15702
16300
|
!preparedMatches ||
|
|
15703
16301
|
(record &&
|
|
15704
16302
|
(!record.tabId ||
|
|
15705
|
-
record.
|
|
15706
|
-
resolveAiGuideProtocolVersion(this.bootstrap) ||
|
|
15707
|
-
record.experienceMode !==
|
|
15708
|
-
(this.bootstrap.experienceMode ?? "standard"))) ||
|
|
16303
|
+
!resumeProtocolIsSupported(record, this.bootstrap))) ||
|
|
15709
16304
|
(pendingLaunch && this.visitorActiveTakeoverOnly)) {
|
|
15710
16305
|
abandonUnusableCommit();
|
|
15711
16306
|
return;
|
|
@@ -15717,7 +16312,7 @@ export default class AiLiveAssist {
|
|
|
15717
16312
|
return;
|
|
15718
16313
|
}
|
|
15719
16314
|
if (this.lastLaunch &&
|
|
15720
|
-
this.lastLaunch
|
|
16315
|
+
!this.pendingLaunchMatches(this.lastLaunch, stagedPending)) {
|
|
15721
16316
|
this.clearStagedPendingLaunch(stagedPending, authority);
|
|
15722
16317
|
}
|
|
15723
16318
|
else {
|
|
@@ -15729,6 +16324,12 @@ export default class AiLiveAssist {
|
|
|
15729
16324
|
return;
|
|
15730
16325
|
}
|
|
15731
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
|
+
}
|
|
15732
16333
|
const effectivePayload = record &&
|
|
15733
16334
|
storedResume?.sessionId === record.sessionId &&
|
|
15734
16335
|
storedResume.resumeCredential !== record.resumeCredential
|
|
@@ -15769,11 +16370,11 @@ export default class AiLiveAssist {
|
|
|
15769
16370
|
completeFocusedTabRelinquish(value) {
|
|
15770
16371
|
const payload = parseFocusedHandoffPayload(value);
|
|
15771
16372
|
const sameResume = Boolean(payload?.resume &&
|
|
15772
|
-
this.focusedHandoffInFlight?.resume
|
|
15773
|
-
|
|
16373
|
+
this.focusedHandoffInFlight?.resume &&
|
|
16374
|
+
this.sameResumeAuthorityIdentity(this.focusedHandoffInFlight.resume, payload.resume));
|
|
15774
16375
|
const samePendingLaunch = Boolean(payload?.pendingLaunch &&
|
|
15775
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15776
|
-
|
|
16376
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16377
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, payload.pendingLaunch));
|
|
15777
16378
|
if (!payload || (!sameResume && !samePendingLaunch)) {
|
|
15778
16379
|
return;
|
|
15779
16380
|
}
|
|
@@ -15804,8 +16405,8 @@ export default class AiLiveAssist {
|
|
|
15804
16405
|
this.clearStagedPendingLaunch(pendingLaunch, authority);
|
|
15805
16406
|
}
|
|
15806
16407
|
if (pendingLaunch &&
|
|
15807
|
-
this.focusedHandoffInFlight?.pendingLaunch
|
|
15808
|
-
|
|
16408
|
+
this.focusedHandoffInFlight?.pendingLaunch &&
|
|
16409
|
+
this.pendingLaunchMatches(this.focusedHandoffInFlight.pendingLaunch, pendingLaunch)) {
|
|
15809
16410
|
this.focusedHandoffInFlight = null;
|
|
15810
16411
|
if (this.terminalRequestPending ||
|
|
15811
16412
|
this.ended ||
|
|
@@ -15827,7 +16428,7 @@ export default class AiLiveAssist {
|
|
|
15827
16428
|
queueMicrotask(() => {
|
|
15828
16429
|
const retry = this.lastLaunch;
|
|
15829
16430
|
if (!retry ||
|
|
15830
|
-
retry
|
|
16431
|
+
!this.pendingLaunchMatches(retry, pendingLaunch) ||
|
|
15831
16432
|
this.terminalRequestPending ||
|
|
15832
16433
|
this.ended ||
|
|
15833
16434
|
this.destroyed ||
|
|
@@ -15836,7 +16437,9 @@ export default class AiLiveAssist {
|
|
|
15836
16437
|
return;
|
|
15837
16438
|
}
|
|
15838
16439
|
this.lastLaunch = null;
|
|
15839
|
-
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey
|
|
16440
|
+
void this.launch(retry.mode, retry.scopes, retry.idempotencyKey, {
|
|
16441
|
+
pendingLaunch: retry,
|
|
16442
|
+
});
|
|
15840
16443
|
});
|
|
15841
16444
|
return;
|
|
15842
16445
|
}
|
|
@@ -15902,7 +16505,7 @@ export default class AiLiveAssist {
|
|
|
15902
16505
|
this.view &&
|
|
15903
16506
|
!this.visitorActiveTakeoverOnly) {
|
|
15904
16507
|
this.lastLaunch = null;
|
|
15905
|
-
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey);
|
|
16508
|
+
await this.launch(deferredLaunch.mode, deferredLaunch.scopes, deferredLaunch.idempotencyKey, { pendingLaunch: deferredLaunch });
|
|
15906
16509
|
return;
|
|
15907
16510
|
}
|
|
15908
16511
|
this.lock.release();
|