@oxvo/ai-live-assist 7.3.4 → 7.4.15

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.
Files changed (67) hide show
  1. package/cjs/AiLiveAssist.d.ts +506 -6
  2. package/cjs/AiLiveAssist.js +12280 -1267
  3. package/cjs/client.d.ts +51 -10
  4. package/cjs/client.js +193 -58
  5. package/cjs/context.d.ts +15 -5
  6. package/cjs/context.js +568 -73
  7. package/cjs/control.d.ts +72 -7
  8. package/cjs/control.js +598 -71
  9. package/cjs/dialogFocusScope.d.ts +37 -0
  10. package/cjs/dialogFocusScope.js +227 -0
  11. package/cjs/experienceState.js +1 -1
  12. package/cjs/index.d.ts +1 -1
  13. package/cjs/index.js +2 -2
  14. package/cjs/media.d.ts +13 -3
  15. package/cjs/media.js +125 -19
  16. package/cjs/messages.js +22 -3
  17. package/cjs/messengerLoader.d.ts +3 -0
  18. package/cjs/messengerLoader.js +156 -0
  19. package/cjs/pageAssistancePreference.d.ts +78 -0
  20. package/cjs/pageAssistancePreference.js +488 -0
  21. package/cjs/preview.d.ts +33 -0
  22. package/cjs/preview.js +567 -0
  23. package/cjs/tabLock.d.ts +54 -3
  24. package/cjs/tabLock.js +752 -72
  25. package/cjs/types.d.ts +101 -3
  26. package/cjs/ui.d.ts +192 -10
  27. package/cjs/ui.js +6683 -543
  28. package/cjs/version.d.ts +1 -1
  29. package/cjs/version.js +1 -1
  30. package/cjs/voicePresenceUi.d.ts +42 -9
  31. package/cjs/voicePresenceUi.js +757 -125
  32. package/lib/AiLiveAssist.d.ts +506 -6
  33. package/lib/AiLiveAssist.js +12285 -1275
  34. package/lib/client.d.ts +51 -10
  35. package/lib/client.js +190 -57
  36. package/lib/context.d.ts +15 -5
  37. package/lib/context.js +566 -72
  38. package/lib/control.d.ts +72 -7
  39. package/lib/control.js +598 -71
  40. package/lib/dialogFocusScope.d.ts +37 -0
  41. package/lib/dialogFocusScope.js +223 -0
  42. package/lib/experienceState.js +1 -1
  43. package/lib/index.d.ts +1 -1
  44. package/lib/index.js +2 -2
  45. package/lib/media.d.ts +13 -3
  46. package/lib/media.js +125 -19
  47. package/lib/messages.js +22 -3
  48. package/lib/messengerLoader.d.ts +3 -0
  49. package/lib/messengerLoader.js +151 -0
  50. package/lib/pageAssistancePreference.d.ts +78 -0
  51. package/lib/pageAssistancePreference.js +482 -0
  52. package/lib/preview.d.ts +33 -0
  53. package/lib/preview.js +561 -0
  54. package/lib/tabLock.d.ts +54 -3
  55. package/lib/tabLock.js +752 -72
  56. package/lib/types.d.ts +101 -3
  57. package/lib/ui.d.ts +192 -10
  58. package/lib/ui.js +6684 -544
  59. package/lib/version.d.ts +1 -1
  60. package/lib/version.js +1 -1
  61. package/lib/voicePresenceUi.d.ts +42 -9
  62. package/lib/voicePresenceUi.js +757 -125
  63. package/package.json +8 -3
  64. package/cjs/recordingPolicy.d.ts +0 -2
  65. package/cjs/recordingPolicy.js +0 -12
  66. package/lib/recordingPolicy.d.ts +0 -2
  67. package/lib/recordingPolicy.js +0 -8
package/cjs/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AssistMode, BootstrapConfig, ConsentScope, LaunchResult, ExperienceMode } from './types.js';
1
+ import type { AssistMode, BootstrapConfig, ConsentScope, LaunchResult, ExperienceMode } from "./types.js";
2
2
  export declare class RuntimeClientError extends Error {
3
3
  readonly code: string;
4
4
  readonly retryable: boolean;
@@ -6,17 +6,22 @@ export declare class RuntimeClientError extends Error {
6
6
  readonly status: number;
7
7
  constructor(code: string, message: string, retryable: boolean, retryAfterMs: number | null, status: number);
8
8
  }
9
- type LaunchInput = {
9
+ export declare const MAX_ASSIST_SESSION_GRANT_LENGTH = 4096;
10
+ export declare const isValidReplaySessionId: (value: unknown) => value is string;
11
+ export declare const isValidAssistSessionGrant: (value: unknown) => value is string;
12
+ export type ReplayLaunchContext = {
13
+ sessionId: string;
14
+ tabId: string;
15
+ grant: string;
16
+ };
17
+ export type LaunchInput = {
10
18
  siteKey: string;
11
19
  configRevision: number;
12
20
  path: string;
13
21
  tabId: string;
14
22
  mode: AssistMode;
15
23
  anonymousId: string;
16
- replay?: {
17
- sessionId: string;
18
- tabId: string;
19
- };
24
+ replay: ReplayLaunchContext;
20
25
  disclosureChecksum: string;
21
26
  scopes: ConsentScope[];
22
27
  locale: string;
@@ -26,6 +31,39 @@ type LaunchInput = {
26
31
  experienceMode: ExperienceMode;
27
32
  fallbackIntent?: "standard_assist" | "human_handoff";
28
33
  lowPower: boolean;
34
+ idempotencyKey: string;
35
+ };
36
+ export type ModeUpgradeResumeInput = {
37
+ sessionId: string;
38
+ resumeCredential: string;
39
+ path: string;
40
+ protocolVersion: 1 | 2;
41
+ requestId: string;
42
+ idempotencyKey: string;
43
+ };
44
+ export type ModeUpgradeRecoveryResult = {
45
+ schemaVersion: 1;
46
+ status: "voice_committed";
47
+ resume: LaunchResult;
48
+ } | {
49
+ schemaVersion: 1;
50
+ status: "terminalized";
51
+ sessionId: string;
52
+ state: "ended" | "failed" | "revoked";
53
+ };
54
+ export type RuntimeSessionState = "created" | "authorizing" | "connecting" | "active_text" | "active_voice" | "paused" | "reconnecting" | "handoff_pending" | "human_controlled" | "ending" | "ended" | "failed" | "revoked";
55
+ export type PauseCommandIdentity = {
56
+ operationId: string;
57
+ clientSequence: number;
58
+ };
59
+ export type PauseCommandResult = {
60
+ schemaVersion: 1;
61
+ operationId: string;
62
+ clientSequence: number;
63
+ fencingToken: number;
64
+ action: "pause" | "resume";
65
+ status: "applied" | "replayed" | "superseded";
66
+ state: RuntimeSessionState;
29
67
  };
30
68
  export declare const idempotencyKey: (prefix: string) => string;
31
69
  export declare class RuntimeClient {
@@ -35,19 +73,22 @@ export declare class RuntimeClient {
35
73
  bootstrap(siteKey: string, path: string, anonymousId: string, signal: AbortSignal): Promise<BootstrapConfig>;
36
74
  launch(input: LaunchInput, signal: AbortSignal): Promise<LaunchResult>;
37
75
  resume(sessionId: string, credential: string, path: string, protocolVersion: 1 | 2, signal: AbortSignal): Promise<LaunchResult>;
38
- offer(result: LaunchResult, offerSdp: string, signal: AbortSignal): Promise<{
76
+ resumeModeUpgrade(input: ModeUpgradeResumeInput, signal: AbortSignal): Promise<LaunchResult>;
77
+ reconcileModeUpgrade(input: ModeUpgradeResumeInput, signal?: AbortSignal, keepalive?: boolean): Promise<ModeUpgradeRecoveryResult>;
78
+ offer(result: LaunchResult, offerSdp: string, signal: AbortSignal, offerIdempotencyKey?: string): Promise<{
39
79
  answerSdp: string;
40
80
  fencingToken: number;
81
+ sessionState: "active_text" | "active_voice" | null;
41
82
  }>;
42
83
  end(result: LaunchResult, fencingToken: number, signal?: AbortSignal): Promise<void>;
43
- pauseOrResume(result: LaunchResult, fencingToken: number, operation: 'pause' | 'resume', signal?: AbortSignal): Promise<void>;
84
+ recoverAndEnd(sessionId: string, resumeCredential: string, protocolVersion: 1 | 2, signal?: AbortSignal): Promise<void>;
85
+ pauseOrResume(result: LaunchResult, fencingToken: number, operation: "pause" | "resume", identity: PauseCommandIdentity, signal?: AbortSignal): Promise<PauseCommandResult>;
44
86
  feedback(result: LaunchResult, payload: {
45
87
  rating: number | null;
46
- outcome: 'resolved' | 'partially_resolved' | 'not_resolved' | 'handoff';
88
+ outcome: "resolved" | "partially_resolved" | "not_resolved" | "handoff";
47
89
  }, signal?: AbortSignal): Promise<void>;
48
90
  private postEmpty;
49
91
  private postJson;
50
92
  private parseJson;
51
93
  private throwResponse;
52
94
  }
53
- export {};
package/cjs/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RuntimeClient = exports.idempotencyKey = exports.RuntimeClientError = void 0;
3
+ exports.RuntimeClient = exports.idempotencyKey = exports.isValidAssistSessionGrant = exports.isValidReplaySessionId = exports.MAX_ASSIST_SESSION_GRANT_LENGTH = exports.RuntimeClientError = void 0;
4
4
  class RuntimeClientError extends Error {
5
5
  constructor(code, message, retryable, retryAfterMs, status) {
6
6
  super(message);
@@ -8,10 +8,40 @@ class RuntimeClientError extends Error {
8
8
  this.retryable = retryable;
9
9
  this.retryAfterMs = retryAfterMs;
10
10
  this.status = status;
11
- this.name = 'RuntimeClientError';
11
+ this.name = "RuntimeClientError";
12
12
  }
13
13
  }
14
14
  exports.RuntimeClientError = RuntimeClientError;
15
+ const MAX_SIGNED_INT64 = "9223372036854775807";
16
+ const CONTROL_OPERATION_ID_PATTERN = /^[A-Za-z0-9_.:-]{8,128}$/;
17
+ exports.MAX_ASSIST_SESSION_GRANT_LENGTH = 4096;
18
+ const isValidReplaySessionId = (value) => {
19
+ if (typeof value !== "string" || !/^[1-9]\d{0,18}$/.test(value)) {
20
+ return false;
21
+ }
22
+ return value.length < MAX_SIGNED_INT64.length || value <= MAX_SIGNED_INT64;
23
+ };
24
+ exports.isValidReplaySessionId = isValidReplaySessionId;
25
+ const isValidAssistSessionGrant = (value) => typeof value === "string" &&
26
+ value.length > 0 &&
27
+ value.length <= exports.MAX_ASSIST_SESSION_GRANT_LENGTH &&
28
+ value.trim() === value;
29
+ exports.isValidAssistSessionGrant = isValidAssistSessionGrant;
30
+ const RUNTIME_SESSION_STATES = new Set([
31
+ "created",
32
+ "authorizing",
33
+ "connecting",
34
+ "active_text",
35
+ "active_voice",
36
+ "paused",
37
+ "reconnecting",
38
+ "handoff_pending",
39
+ "human_controlled",
40
+ "ending",
41
+ "ended",
42
+ "failed",
43
+ "revoked",
44
+ ]);
15
45
  const requestId = () => `req_${crypto.randomUUID()}`;
16
46
  const idempotencyKey = (prefix) => `${prefix}_${crypto.randomUUID()}`;
17
47
  exports.idempotencyKey = idempotencyKey;
@@ -19,31 +49,53 @@ class RuntimeClient {
19
49
  constructor(runtimeUrl, clientVersion) {
20
50
  this.clientVersion = clientVersion;
21
51
  const url = new URL(runtimeUrl, window.location.href);
22
- if (!['http:', 'https:'].includes(url.protocol)) {
23
- throw new Error('AI Guide runtime URL must use HTTP or HTTPS.');
52
+ if (!["http:", "https:"].includes(url.protocol)) {
53
+ throw new Error("AI Guide runtime URL must use HTTP or HTTPS.");
24
54
  }
25
- this.baseUrl = url.origin + url.pathname.replace(/\/$/, '');
55
+ this.baseUrl = url.origin + url.pathname.replace(/\/$/, "");
26
56
  }
27
57
  async bootstrap(siteKey, path, anonymousId, signal) {
28
58
  const url = new URL(`${this.baseUrl}/ai-live-assist/v1/public/bootstrap`);
29
- url.searchParams.set('siteKey', siteKey);
30
- url.searchParams.set('path', path);
59
+ url.searchParams.set("siteKey", siteKey);
60
+ url.searchParams.set("path", path);
31
61
  const response = await fetch(url, {
32
- method: 'GET',
33
- mode: 'cors',
34
- credentials: 'omit',
35
- cache: 'no-store',
62
+ method: "GET",
63
+ mode: "cors",
64
+ credentials: "omit",
65
+ cache: "no-store",
36
66
  headers: {
37
- Accept: 'application/json',
38
- 'X-Oxvo-Ai-Client-Version': this.clientVersion,
39
- 'X-Oxvo-Ai-Visitor-Id': anonymousId,
40
- 'X-Request-Id': requestId(),
67
+ Accept: "application/json",
68
+ "X-Oxvo-Ai-Client-Version": this.clientVersion,
69
+ "X-Oxvo-Ai-Visitor-Id": anonymousId,
70
+ "X-Request-Id": requestId(),
41
71
  },
42
72
  signal,
43
73
  });
44
74
  return this.parseJson(response);
45
75
  }
46
76
  async launch(input, signal) {
77
+ if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(input.idempotencyKey)) {
78
+ throw new Error("Invalid AI Guide launch idempotency key.");
79
+ }
80
+ const replaySessionId = input.replay?.sessionId;
81
+ const replayTabId = input.replay?.tabId;
82
+ const replayGrant = input.replay?.grant;
83
+ if (!(0, exports.isValidReplaySessionId)(replaySessionId)) {
84
+ throw new Error("Invalid AI Guide replay session ID.");
85
+ }
86
+ if (typeof replayTabId !== "string" ||
87
+ replayTabId !== input.tabId ||
88
+ !/^[A-Za-z0-9_.:-]{1,128}$/.test(replayTabId)) {
89
+ throw new Error("Invalid AI Guide replay tab ID.");
90
+ }
91
+ if (!(0, exports.isValidAssistSessionGrant)(replayGrant)) {
92
+ throw new Error("Invalid AI Guide replay grant.");
93
+ }
94
+ const replay = {
95
+ sessionId: replaySessionId,
96
+ tabId: replayTabId,
97
+ grant: replayGrant,
98
+ };
47
99
  const shared = {
48
100
  clientVersion: this.clientVersion,
49
101
  siteKey: input.siteKey,
@@ -52,7 +104,7 @@ class RuntimeClient {
52
104
  tabId: input.tabId,
53
105
  mode: input.mode,
54
106
  visitor: { anonymousId: input.anonymousId },
55
- ...(input.replay ? { replay: input.replay } : {}),
107
+ replay,
56
108
  consent: {
57
109
  disclosureChecksum: input.disclosureChecksum,
58
110
  scopes: input.scopes,
@@ -70,13 +122,12 @@ class RuntimeClient {
70
122
  ...shared,
71
123
  capabilitySummary: {
72
124
  microphone: input.microphone,
73
- webrtc: typeof RTCPeerConnection !== 'undefined',
74
- audioPlayback: typeof HTMLAudioElement !== 'undefined',
75
- webAudio: typeof AudioContext !== 'undefined' ||
76
- typeof window
77
- .webkitAudioContext !== 'undefined',
78
- svgOverlay: typeof SVGElement !== 'undefined',
79
- visualViewport: typeof window.visualViewport !== 'undefined',
125
+ webrtc: typeof RTCPeerConnection !== "undefined",
126
+ audioPlayback: typeof HTMLAudioElement !== "undefined",
127
+ webAudio: typeof AudioContext !== "undefined" ||
128
+ typeof window.webkitAudioContext !== "undefined",
129
+ svgOverlay: typeof SVGElement !== "undefined",
130
+ visualViewport: typeof window.visualViewport !== "undefined",
80
131
  reducedMotion: input.reducedMotion,
81
132
  lowPower: input.lowPower,
82
133
  },
@@ -87,79 +138,163 @@ class RuntimeClient {
87
138
  ...shared,
88
139
  capabilitySummary: {
89
140
  microphone: input.microphone,
90
- webrtc: typeof RTCPeerConnection !== 'undefined',
141
+ webrtc: typeof RTCPeerConnection !== "undefined",
91
142
  reducedMotion: input.reducedMotion,
92
143
  },
93
144
  };
94
- return this.postJson('/ai-live-assist/v1/public/launch', body, {
95
- 'Idempotency-Key': (0, exports.idempotencyKey)('launch'),
145
+ return this.postJson("/ai-live-assist/v1/public/launch", body, {
146
+ "Idempotency-Key": input.idempotencyKey,
96
147
  }, signal);
97
148
  }
98
149
  async resume(sessionId, credential, path, protocolVersion, signal) {
99
- return this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(sessionId)}/resume`, {
150
+ const request = this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(sessionId)}/resume`, {
100
151
  schemaVersion: protocolVersion,
101
152
  protocolVersion,
102
153
  path,
103
- reason: 'navigation',
154
+ reason: "navigation",
104
155
  }, { Authorization: `Bearer ${credential}` }, signal);
156
+ try {
157
+ return await request;
158
+ }
159
+ catch (error) {
160
+ if (error instanceof TypeError) {
161
+ throw new RuntimeClientError("RUNTIME_UNAVAILABLE", "AI Guide is temporarily unavailable.", true, null, 0);
162
+ }
163
+ throw error;
164
+ }
165
+ }
166
+ async resumeModeUpgrade(input, signal) {
167
+ if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(input.requestId) ||
168
+ !/^[A-Za-z0-9_.:-]{8,128}$/.test(input.idempotencyKey)) {
169
+ throw new Error("Invalid AI Guide mode-upgrade identity.");
170
+ }
171
+ return this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(input.sessionId)}/resume`, {
172
+ schemaVersion: input.protocolVersion,
173
+ protocolVersion: input.protocolVersion,
174
+ path: input.path,
175
+ reason: "mode_upgrade",
176
+ modeUpgradeRequestId: input.requestId,
177
+ }, {
178
+ Authorization: `Bearer ${input.resumeCredential}`,
179
+ "Idempotency-Key": input.idempotencyKey,
180
+ }, signal);
105
181
  }
106
- async offer(result, offerSdp, signal) {
182
+ async reconcileModeUpgrade(input, signal, keepalive = false) {
183
+ if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(input.requestId) ||
184
+ !/^[A-Za-z0-9_.:-]{8,128}$/.test(input.idempotencyKey)) {
185
+ throw new Error("Invalid AI Guide mode-upgrade recovery identity.");
186
+ }
187
+ return this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(input.sessionId)}/mode-upgrade/reconcile`, {
188
+ schemaVersion: input.protocolVersion,
189
+ protocolVersion: input.protocolVersion,
190
+ path: input.path,
191
+ modeUpgradeRequestId: input.requestId,
192
+ }, {
193
+ Authorization: `Bearer ${input.resumeCredential}`,
194
+ "Idempotency-Key": input.idempotencyKey,
195
+ }, signal, keepalive);
196
+ }
197
+ async offer(result, offerSdp, signal, offerIdempotencyKey = (0, exports.idempotencyKey)("offer")) {
198
+ if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(offerIdempotencyKey)) {
199
+ throw new Error("Invalid AI Guide realtime-offer idempotency key.");
200
+ }
107
201
  const response = await fetch(result.realtimeOfferUrl, {
108
- method: 'POST',
109
- mode: 'cors',
110
- credentials: 'omit',
111
- cache: 'no-store',
202
+ method: "POST",
203
+ mode: "cors",
204
+ credentials: "omit",
205
+ cache: "no-store",
112
206
  headers: {
113
- Accept: 'application/sdp',
207
+ Accept: "application/sdp",
114
208
  Authorization: `Bearer ${result.launchCredential}`,
115
- 'Content-Type': 'application/sdp',
116
- 'Idempotency-Key': (0, exports.idempotencyKey)('offer'),
117
- 'X-Oxvo-Ai-Client-Version': this.clientVersion,
118
- 'X-Request-Id': requestId(),
209
+ "Content-Type": "application/sdp",
210
+ "Idempotency-Key": offerIdempotencyKey,
211
+ "X-Oxvo-Ai-Client-Version": this.clientVersion,
212
+ "X-Request-Id": requestId(),
119
213
  },
120
214
  body: offerSdp,
121
215
  signal,
122
216
  });
123
217
  if (!response.ok)
124
218
  await this.throwResponse(response);
125
- const fencingToken = Number(response.headers.get('X-Oxvo-Fencing-Token'));
219
+ const fencingToken = Number(response.headers.get("X-Oxvo-Fencing-Token"));
126
220
  if (!Number.isSafeInteger(fencingToken) || fencingToken < 1) {
127
- throw new RuntimeClientError('PROTOCOL_INVALID', 'The runtime returned an invalid control lease.', false, null, 502);
221
+ throw new RuntimeClientError("PROTOCOL_INVALID", "The runtime returned an invalid control lease.", false, null, 502);
128
222
  }
129
- return { answerSdp: await response.text(), fencingToken };
223
+ const sessionStateHeader = response.headers.get("X-Oxvo-Session-State");
224
+ const sessionState = sessionStateHeader === "active_text" ||
225
+ sessionStateHeader === "active_voice"
226
+ ? sessionStateHeader
227
+ : null;
228
+ return {
229
+ answerSdp: await response.text(),
230
+ fencingToken,
231
+ sessionState,
232
+ };
130
233
  }
131
234
  async end(result, fencingToken, signal) {
132
- await this.postEmpty(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(result.sessionId)}/end`, result.controlCredential, fencingToken, result.protocolVersion, signal);
235
+ await this.postEmpty(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(result.sessionId)}/end`, result.controlCredential, fencingToken, result.protocolVersion, signal, true);
133
236
  }
134
- async pauseOrResume(result, fencingToken, operation, signal) {
135
- await this.postEmpty(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(result.sessionId)}/${operation}`, result.controlCredential, fencingToken, result.protocolVersion, signal);
237
+ async recoverAndEnd(sessionId, resumeCredential, protocolVersion, signal) {
238
+ await this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(sessionId)}/end`, {
239
+ schemaVersion: protocolVersion,
240
+ protocolVersion,
241
+ fencingToken: 0,
242
+ terminalRecovery: true,
243
+ }, { Authorization: `Bearer ${resumeCredential}` }, signal, true);
244
+ }
245
+ async pauseOrResume(result, fencingToken, operation, identity, signal) {
246
+ if (!CONTROL_OPERATION_ID_PATTERN.test(identity.operationId) ||
247
+ !Number.isSafeInteger(identity.clientSequence) ||
248
+ identity.clientSequence < 1) {
249
+ throw new Error("Invalid AI Guide pause command identity.");
250
+ }
251
+ const response = await this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(result.sessionId)}/actions/${operation}`, {
252
+ schemaVersion: result.protocolVersion,
253
+ protocolVersion: result.protocolVersion,
254
+ fencingToken,
255
+ operationId: identity.operationId,
256
+ clientSequence: identity.clientSequence,
257
+ }, { Authorization: `Bearer ${result.controlCredential}` }, signal);
258
+ if (!response ||
259
+ typeof response !== "object" ||
260
+ response.schemaVersion !== 1 ||
261
+ response.operationId !== identity.operationId ||
262
+ response.clientSequence !== identity.clientSequence ||
263
+ response.fencingToken !== fencingToken ||
264
+ response.action !== operation ||
265
+ !["applied", "replayed", "superseded"].includes(response.status) ||
266
+ !RUNTIME_SESSION_STATES.has(response.state)) {
267
+ throw new RuntimeClientError("PROTOCOL_INVALID", "The runtime returned an invalid pause command result.", false, null, 502);
268
+ }
269
+ return response;
136
270
  }
137
271
  async feedback(result, payload, signal) {
138
272
  await this.postJson(`/ai-live-assist/v1/public/sessions/${encodeURIComponent(result.sessionId)}/feedback`, payload, {
139
273
  Authorization: `Bearer ${result.feedbackCredential}`,
140
274
  }, signal);
141
275
  }
142
- async postEmpty(path, credential, fencingToken, protocolVersion, signal) {
276
+ async postEmpty(path, credential, fencingToken, protocolVersion, signal, keepalive = false) {
143
277
  await this.postJson(path, {
144
278
  schemaVersion: protocolVersion,
145
279
  protocolVersion,
146
280
  fencingToken,
147
- }, { Authorization: `Bearer ${credential}` }, signal);
281
+ }, { Authorization: `Bearer ${credential}` }, signal, keepalive);
148
282
  }
149
- async postJson(path, body, extraHeaders, signal) {
283
+ async postJson(path, body, extraHeaders, signal, keepalive = false) {
150
284
  const response = await fetch(`${this.baseUrl}${path}`, {
151
- method: 'POST',
152
- mode: 'cors',
153
- credentials: 'omit',
154
- cache: 'no-store',
285
+ method: "POST",
286
+ mode: "cors",
287
+ credentials: "omit",
288
+ cache: "no-store",
155
289
  headers: {
156
- Accept: 'application/json',
157
- 'Content-Type': 'application/json',
158
- 'X-Oxvo-Ai-Client-Version': this.clientVersion,
159
- 'X-Request-Id': requestId(),
290
+ Accept: "application/json",
291
+ "Content-Type": "application/json",
292
+ "X-Oxvo-Ai-Client-Version": this.clientVersion,
293
+ "X-Request-Id": requestId(),
160
294
  ...extraHeaders,
161
295
  },
162
296
  body: JSON.stringify(body),
297
+ ...(keepalive ? { keepalive: true } : {}),
163
298
  ...(signal ? { signal } : {}),
164
299
  });
165
300
  if (response.status === 204)
@@ -173,7 +308,7 @@ class RuntimeClient {
173
308
  return (await response.json());
174
309
  }
175
310
  catch {
176
- throw new RuntimeClientError('PROTOCOL_INVALID', 'The runtime returned an invalid response.', false, null, response.status);
311
+ throw new RuntimeClientError("PROTOCOL_INVALID", "The runtime returned an invalid response.", false, null, response.status);
177
312
  }
178
313
  }
179
314
  async throwResponse(response) {
@@ -185,9 +320,9 @@ class RuntimeClient {
185
320
  catch {
186
321
  // Keep the public fallback generic; response bodies may come from a proxy.
187
322
  }
188
- throw new RuntimeClientError(typeof error.code === 'string' ? error.code : 'RUNTIME_UNAVAILABLE', typeof error.message === 'string'
323
+ throw new RuntimeClientError(typeof error.code === "string" ? error.code : "RUNTIME_UNAVAILABLE", typeof error.message === "string"
189
324
  ? error.message
190
- : 'AI Guide is temporarily unavailable.', error.retryable === true, typeof error.retryAfterMs === 'number' ? error.retryAfterMs : null, response.status);
325
+ : "AI Guide is temporarily unavailable.", error.retryable === true, typeof error.retryAfterMs === "number" ? error.retryAfterMs : null, response.status);
191
326
  }
192
327
  }
193
328
  exports.RuntimeClient = RuntimeClient;
package/cjs/context.d.ts CHANGED
@@ -1,17 +1,22 @@
1
- import type { ActionExecutionResult, ActionGrant, PageContextSnapshot, PresentationTarget, VisualContextCapture } from './types.js';
1
+ import type { ActionExecutionResult, ActionGrant, PageContextSnapshot, SanitizedTarget, PresentationTarget, VisualContextCapture } from './types.js';
2
+ export declare const actionTargetFingerprint: (target: SanitizedTarget, subtle?: Pick<SubtleCrypto, "digest"> | undefined) => Promise<string | null>;
2
3
  type SelectorRegionRule = {
3
4
  digest: string;
4
5
  selector: string;
5
6
  };
6
7
  type ContextCallbacks = {
7
8
  onSnapshot: (snapshot: PageContextSnapshot) => void | Promise<void>;
8
- onNavigation: (pageId: string, revision: number, url: string) => void | Promise<void>;
9
+ onActionMutationApplied?: (actionId: string, revision: number, url: string) => void;
10
+ onCausalNavigationObserved?: (pageId: string, revision: number, url: string, causalActionId: string) => void;
11
+ onNavigation: (pageId: string, revision: number, url: string, causalActionId?: string) => boolean | void | Promise<boolean | void>;
9
12
  onHighlight: (element: Element | null, status: 'focus' | 'success' | 'failure') => void;
10
13
  };
11
14
  export declare class PageContextCollector {
12
15
  private readonly callbacks;
13
16
  private readonly selectorRegionRules;
14
17
  private readonly policyVersion;
18
+ private readonly trustedGuideRoot;
19
+ private readonly statefulButtonActionProof;
15
20
  private pageId;
16
21
  private revision;
17
22
  private running;
@@ -28,19 +33,21 @@ export declare class PageContextCollector {
28
33
  private pendingNavigation;
29
34
  private pendingCapture;
30
35
  private deferredNavigation;
36
+ private navigationSettlementGeneration;
37
+ private navigationSettlement;
31
38
  private recentSelection;
32
39
  private latestSemanticSignature;
33
- constructor(callbacks: ContextCallbacks, selectorRegionRules?: SelectorRegionRule[], policyVersion?: number);
40
+ constructor(callbacks: ContextCallbacks, selectorRegionRules?: SelectorRegionRule[], policyVersion?: number, trustedGuideRoot?: Element | null, statefulButtonActionProof?: boolean);
34
41
  get currentRevision(): number;
35
42
  get currentPageId(): string;
36
43
  presentationTarget(targetId: string, revision: number): PresentationTarget | null;
37
44
  start(): void;
38
45
  stop(): void;
39
- captureAndPublish(): Promise<PageContextSnapshot>;
46
+ captureAndPublish(): Promise<PageContextSnapshot | null>;
40
47
  capture(): PageContextSnapshot;
41
48
  private captureSnapshot;
42
49
  captureVisualContext(): VisualContextCapture | null;
43
- execute(grant: ActionGrant): Promise<ActionExecutionResult>;
50
+ execute(grant: ActionGrant, authorityIsCurrent?: () => boolean): Promise<ActionExecutionResult>;
44
51
  completeActionExecution(actionId: string): Promise<string | null>;
45
52
  private click;
46
53
  private type;
@@ -59,6 +66,9 @@ export declare class PageContextCollector {
59
66
  private handleControlStateChange;
60
67
  private readSafeSelection;
61
68
  private handleNavigation;
69
+ private beginNavigationSettlement;
70
+ private observeCausalNavigationAfterMutation;
71
+ private reportCausalNavigationObservation;
62
72
  private installNavigationHooks;
63
73
  private restoreNavigationHooks;
64
74
  private reducedMotion;