@mearl/browser-core 3.0.0-beta.4 → 3.0.0-beta.7

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 (47) hide show
  1. package/README.md +2 -2
  2. package/dist/agentChat.d.ts +2 -0
  3. package/dist/agentSkills.d.ts +1 -1
  4. package/dist/app-profile-runtime/types.d.ts +10 -12
  5. package/dist/appProfileDefinition.d.ts +32 -14
  6. package/dist/appProfileDefinition.js +63 -69
  7. package/dist/axSnapshot.js +1 -1
  8. package/dist/boundedRequestBuffer.d.ts +4 -4
  9. package/dist/browserActionProtocol.d.ts +65 -42
  10. package/dist/browserActionProtocol.js +15 -15
  11. package/dist/browserCapabilities.d.ts +47 -0
  12. package/dist/browserCapabilities.js +63 -0
  13. package/dist/browserDiscovery.d.ts +3 -0
  14. package/dist/browserDiscovery.js +20 -0
  15. package/dist/browserEnhancement.d.ts +161 -0
  16. package/dist/browserEnhancement.js +80 -0
  17. package/dist/cookie.d.ts +6 -0
  18. package/dist/cookie.js +20 -5
  19. package/dist/dataStructure.d.ts +8 -0
  20. package/dist/dataStructure.js +58 -0
  21. package/dist/deviceEnvironmentObservation.d.ts +48 -0
  22. package/dist/deviceEnvironmentObservation.js +77 -0
  23. package/dist/domClicking.d.ts +14 -0
  24. package/dist/domClicking.js +98 -10
  25. package/dist/domInspection.js +13 -1
  26. package/dist/extensionServiceProtocol.d.ts +7 -0
  27. package/dist/extensionServiceProtocol.js +1 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +8 -0
  30. package/dist/pageAct.d.ts +2 -0
  31. package/dist/pageAct.js +42 -10
  32. package/dist/pageDialog.d.ts +66 -0
  33. package/dist/pageDialog.js +175 -0
  34. package/dist/pageObserver.js +16 -5
  35. package/dist/pagePressing.d.ts +1 -1
  36. package/dist/providerAppResources.d.ts +39 -0
  37. package/dist/providerAppResources.js +2 -0
  38. package/dist/proxyPermissions.js +4 -3
  39. package/dist/requestMetadata.d.ts +9 -0
  40. package/dist/requestMetadata.js +8 -0
  41. package/dist/requestSerializer.d.ts +2 -2
  42. package/dist/requestSerializer.js +1 -29
  43. package/dist/requestStreamCapture.d.ts +5 -3
  44. package/dist/requestStreamCapture.js +2 -0
  45. package/dist/runActions.js +1 -1
  46. package/dist/types.d.ts +4 -2
  47. package/package.json +2 -2
@@ -28,6 +28,7 @@ export function isExtensionUiServiceAction(value) {
28
28
  isCloudConnectorAction(value));
29
29
  }
30
30
  export const HOST_TO_EXTENSION_ACTIONS = {
31
+ __browser_enhancement: true,
31
32
  __preset_installation: true,
32
33
  __preset_browser: true,
33
34
  __export_cookies: true,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from './appProfileDefinition.js';
2
2
  export * from './types.js';
3
+ export * from './browserEnhancement.js';
4
+ export * from './browserDiscovery.js';
5
+ export * from './providerAppResources.js';
3
6
  export * from './eventFilters.js';
4
7
  export * from './requestSerializer.js';
5
8
  export * from './boundedRequestBuffer.js';
@@ -10,6 +13,7 @@ export * from './domScrolling.js';
10
13
  export * from './domInspection.js';
11
14
  export * from './domSnapshot.js';
12
15
  export * from './deviceEmulation.js';
16
+ export * from './deviceEnvironmentObservation.js';
13
17
  export * from './debuggerTarget.js';
14
18
  export * from './rootWebViewNavigation.js';
15
19
  export type { AppBridgeStyle, AppBridgeSpec, AppBridgeBehavior, BridgeActionMap, AppSchemeRule, AppMiniProgramConfig, AppMiniProgramPaymentRoute, AppMiniProgramMessageAction, AppMiniProgramMessageRule, AppMiniProgramNativeAction, AppMiniProgramNativeRoute, AppImagePickerConfig, AppOverlayConfig, AppNativeBackConfig, AppLayerUrlProxyConfig, AppTitleBarConfig, AppTitleBarHostStyle, AppSafeAreaInsets, AppRootContainerCookie, AppRootContainerConfig, InjectedAppProfileConfig, AppProfileHostCapabilities, } from './app-profile-runtime/types.js';
@@ -21,6 +25,7 @@ export * from './axSnapshot.js';
21
25
  export * from './pageObserver.js';
22
26
  export * from './navigationTracker.js';
23
27
  export * from './pageAct.js';
28
+ export * from './pageDialog.js';
24
29
  export * from './backendNodeRefs.js';
25
30
  export * from './pageEffectRefs.js';
26
31
  export * from './pageWait.js';
@@ -37,6 +42,7 @@ export * from './pluginPages.js';
37
42
  export * from './pluginPageRuntime.js';
38
43
  export * from './pluginPageHost.js';
39
44
  export * from './browserActionProtocol.js';
45
+ export * from './browserCapabilities.js';
40
46
  export * from './runActions.js';
41
47
  export * from './sendRequestFiles.js';
42
48
  export * from './sendRequestHeaders.js';
@@ -57,3 +63,5 @@ export * from './consumeSseResponse.js';
57
63
  export { parseQueryString } from './queryString.js';
58
64
  export * from './extensionVersion.js';
59
65
  export { MEARL_NATIVE_HOST_NAMES, type NativeHostIntegrationMode } from './nativeHostNames.js';
66
+ export * from './dataStructure.js';
67
+ export * from './requestMetadata.js';
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from './appProfileDefinition.js';
2
2
  export * from './types.js';
3
+ export * from './browserEnhancement.js';
4
+ export * from './browserDiscovery.js';
5
+ export * from './providerAppResources.js';
3
6
  export * from './eventFilters.js';
4
7
  export * from './requestSerializer.js';
5
8
  export * from './boundedRequestBuffer.js';
@@ -10,6 +13,7 @@ export * from './domScrolling.js';
10
13
  export * from './domInspection.js';
11
14
  export * from './domSnapshot.js';
12
15
  export * from './deviceEmulation.js';
16
+ export * from './deviceEnvironmentObservation.js';
13
17
  export * from './debuggerTarget.js';
14
18
  export * from './rootWebViewNavigation.js';
15
19
  export * from './axTextMatch.js';
@@ -20,6 +24,7 @@ export * from './axSnapshot.js';
20
24
  export * from './pageObserver.js';
21
25
  export * from './navigationTracker.js';
22
26
  export * from './pageAct.js';
27
+ export * from './pageDialog.js';
23
28
  export * from './backendNodeRefs.js';
24
29
  export * from './pageEffectRefs.js';
25
30
  export * from './pageWait.js';
@@ -36,6 +41,7 @@ export * from './pluginPages.js';
36
41
  export * from './pluginPageRuntime.js';
37
42
  export * from './pluginPageHost.js';
38
43
  export * from './browserActionProtocol.js';
44
+ export * from './browserCapabilities.js';
39
45
  export * from './runActions.js';
40
46
  export * from './sendRequestFiles.js';
41
47
  export * from './sendRequestHeaders.js';
@@ -56,3 +62,5 @@ export * from './consumeSseResponse.js';
56
62
  export { parseQueryString } from './queryString.js';
57
63
  export * from './extensionVersion.js';
58
64
  export { MEARL_NATIVE_HOST_NAMES } from './nativeHostNames.js';
65
+ export * from './dataStructure.js';
66
+ export * from './requestMetadata.js';
package/dist/pageAct.d.ts CHANGED
@@ -61,6 +61,7 @@ export interface PageActNavigationObserver {
61
61
  dispose(): void | Promise<void>;
62
62
  }
63
63
  export interface PageActDependencies {
64
+ dialogs?: import('./pageDialog.js').PageDialogState;
64
65
  startObservation(): Promise<PageObservationCheckpoint>;
65
66
  finishObservation(checkpoint: PageObservationCheckpoint, options: PageObservationWaitOptions): Promise<PageActObservation>;
66
67
  startNavigationObserver(options: {
@@ -89,6 +90,7 @@ interface PageActUnavailableObservation {
89
90
  export type PageActObservation = PageChangeSet | PageNavigationChange | PageActUnavailableObservation;
90
91
  export type PageActObservationResponse = (Omit<PageChangeSet, 'url' | 'title'> & Partial<Pick<PageChangeSet, 'url' | 'title'>>) | PageNavigationChange | PageActUnavailableObservation;
91
92
  export interface PageActResult {
93
+ dialog?: import('./pageDialog.js').PageDialogInfo;
92
94
  action: {
93
95
  name: string;
94
96
  success: boolean;
package/dist/pageAct.js CHANGED
@@ -209,7 +209,21 @@ export async function runPageAct(input, dependencies) {
209
209
  throw new Error('actionData must be an object');
210
210
  }
211
211
  const typedAction = action;
212
- const checkpoint = await dependencies.startObservation();
212
+ const dialogs = dependencies.dialogs;
213
+ const guarded = (work) => dialogs ? dialogs.command(work) : work();
214
+ const dialogResult = (failure, data) => ({
215
+ action: { name: typedAction, success: !failure, ...(data !== undefined ? { data } : {}), ...failure },
216
+ ...(dialogs?.info() ? { dialog: dialogs.info() } : {}),
217
+ });
218
+ let checkpoint;
219
+ try {
220
+ checkpoint = await guarded(() => dependencies.startObservation());
221
+ }
222
+ catch (error) {
223
+ if (!dialogs?.pending)
224
+ throw error;
225
+ return dialogResult({ ...describePageActionFailure(error), stage: 'precondition' });
226
+ }
213
227
  const navigationOptions = resolveNavigationWaitOptions(observe);
214
228
  const sourceTabId = typeof actionData.tabId === 'number' && Number.isInteger(actionData.tabId)
215
229
  ? actionData.tabId
@@ -232,6 +246,14 @@ export async function runPageAct(input, dependencies) {
232
246
  catch (error) {
233
247
  actionFailure = describePageActionFailure(error);
234
248
  }
249
+ const dispose = () => Promise.allSettled([
250
+ Promise.resolve().then(() => navigationObserver?.dispose()),
251
+ Promise.resolve().then(() => dependencies.disposeObservation?.()),
252
+ ]);
253
+ if (dialogs?.pending) {
254
+ await dispose();
255
+ return dialogResult(actionFailure, actionResult);
256
+ }
235
257
  if (actionFailure?.stage === 'precondition') {
236
258
  await Promise.allSettled([
237
259
  Promise.resolve().then(() => navigationObserver?.dispose()),
@@ -248,11 +270,15 @@ export async function runPageAct(input, dependencies) {
248
270
  }
249
271
  let observation;
250
272
  try {
251
- observation = await dependencies.finishObservation(checkpoint, observe);
273
+ observation = await guarded(() => dependencies.finishObservation(checkpoint, observe));
252
274
  }
253
275
  catch (error) {
254
276
  observation = unavailableObservation(checkpoint, error);
255
277
  }
278
+ if (dialogs?.pending) {
279
+ await dispose();
280
+ return dialogResult(actionFailure, actionResult);
281
+ }
256
282
  const navigationInterruptedAction = isNavigationInterruptedActionFailure(actionFailure);
257
283
  if (navigationInterruptedAction && observation.mode === 'navigation') {
258
284
  actionFailure = undefined;
@@ -277,22 +303,22 @@ export async function runPageAct(input, dependencies) {
277
303
  (navigationObserver.hasStarted() ||
278
304
  shouldWaitForPotentialNavigation(typedAction, observation))) {
279
305
  const didNavigate = navigationObserver.hasStarted() ||
280
- (await navigationObserver.waitForStart(navigationOptions.graceMs));
306
+ (await guarded(() => navigationObserver.waitForStart(navigationOptions.graceMs)));
281
307
  if (didNavigate) {
282
- observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
308
+ observation = await guarded(() => navigationObserver.waitForResult(navigationOptions.timeoutMs));
283
309
  }
284
310
  }
285
311
  if (navigationObserver &&
286
312
  (actionFailure === undefined || navigationInterruptedAction) &&
287
313
  observation.mode !== 'navigation' &&
288
314
  navigationObserver.hasNetworkActivity()) {
289
- const network = await navigationObserver.waitForNetworkIdle(navigationOptions.timeoutMs);
315
+ const network = await guarded(() => navigationObserver.waitForNetworkIdle(navigationOptions.timeoutMs));
290
316
  if (navigationObserver.hasStarted()) {
291
- observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
317
+ observation = await guarded(() => navigationObserver.waitForResult(navigationOptions.timeoutMs));
292
318
  }
293
319
  else {
294
320
  try {
295
- observation = await dependencies.finishObservation(checkpoint, observe);
321
+ observation = await guarded(() => dependencies.finishObservation(checkpoint, observe));
296
322
  }
297
323
  catch (error) {
298
324
  observation = unavailableObservation(checkpoint, error);
@@ -309,7 +335,7 @@ export async function runPageAct(input, dependencies) {
309
335
  }
310
336
  if (dependencies.finalizeObservation) {
311
337
  try {
312
- observation = await dependencies.finalizeObservation(observation);
338
+ observation = await guarded(() => dependencies.finalizeObservation(observation));
313
339
  }
314
340
  catch {
315
341
  // Ref enrichment and other response finalizers are best-effort and must
@@ -317,19 +343,25 @@ export async function runPageAct(input, dependencies) {
317
343
  }
318
344
  }
319
345
  if (navigationObserver) {
320
- const openedTabs = await navigationObserver
321
- .getOpenedTabs(navigationOptions.timeoutMs)
346
+ const openedTabs = await guarded(() => navigationObserver.getOpenedTabs(navigationOptions.timeoutMs))
322
347
  .catch(() => []);
323
348
  if (openedTabs.length > 0)
324
349
  observation = { ...observation, openedTabs };
325
350
  }
326
351
  }
352
+ catch (error) {
353
+ if (!dialogs?.pending)
354
+ throw error;
355
+ return dialogResult(actionFailure, actionResult);
356
+ }
327
357
  finally {
328
358
  await Promise.allSettled([
329
359
  Promise.resolve().then(() => navigationObserver?.dispose()),
330
360
  Promise.resolve().then(() => dependencies.disposeObservation?.()),
331
361
  ]);
332
362
  }
363
+ if (dialogs?.pending)
364
+ return dialogResult(actionFailure, actionResult);
333
365
  return {
334
366
  action: {
335
367
  name: typedAction,
@@ -0,0 +1,66 @@
1
+ export interface PageDialogInfo {
2
+ id: string;
3
+ type: 'alert' | 'confirm' | 'prompt' | 'beforeunload';
4
+ message: string;
5
+ defaultPrompt?: string;
6
+ truncated?: true;
7
+ }
8
+ export declare class PageDialogInterruptedError extends Error {
9
+ readonly pageActionFailureStage: 'precondition' | 'dispatch-uncertain';
10
+ readonly code = "DIALOG_PENDING";
11
+ readonly dialog?: PageDialogInfo;
12
+ readonly details: {
13
+ dialog?: PageDialogInfo;
14
+ };
15
+ constructor(dialog?: PageDialogInfo, pageActionFailureStage?: 'precondition' | 'dispatch-uncertain');
16
+ }
17
+ /** Host-side state only: no page injection, implicit acceptance, or action replay. */
18
+ export declare class PageDialogState {
19
+ private epoch;
20
+ private readonly prefix;
21
+ private current?;
22
+ private defaultPromptTruncated;
23
+ private readonly work;
24
+ private readonly listeners;
25
+ private handling?;
26
+ onIdle?: () => void;
27
+ get pending(): boolean;
28
+ get busy(): boolean;
29
+ info(maxChars?: number): PageDialogInfo | undefined;
30
+ opened(event: {
31
+ type?: unknown;
32
+ message?: unknown;
33
+ defaultPrompt?: unknown;
34
+ }): void;
35
+ closed(): void;
36
+ invalidate(): void;
37
+ assertCanDispatch(): void;
38
+ /** Register the actual handler, including its finally; never retain a raw browser Promise here. */
39
+ execute<T>(work: () => Promise<T>, interrupt?: boolean): Promise<T>;
40
+ /** Every real eval/input dispatch must pass here, so a late ack cannot resume its handler. */
41
+ command<T>(work: () => Promise<T>): Promise<T>;
42
+ private race;
43
+ press(input: {
44
+ dialogId?: string;
45
+ key: string;
46
+ promptText?: string;
47
+ selector?: string;
48
+ modifiers?: unknown[];
49
+ pressMode?: string;
50
+ }, send: (params: {
51
+ accept: boolean;
52
+ promptText?: string;
53
+ }) => Promise<unknown>): Promise<{
54
+ pressed: true;
55
+ key: string;
56
+ dispatchMode: 'native-dialog';
57
+ }>;
58
+ private notifyIdle;
59
+ }
60
+ export declare function pageDialogSnapshot(dialogs: PageDialogState, mode?: string, maxChars?: number): {
61
+ snapshot: string;
62
+ mode: string;
63
+ elementCount: number;
64
+ outputNodeCount: number;
65
+ dialog: PageDialogInfo;
66
+ } | undefined;
@@ -0,0 +1,175 @@
1
+ import { PageActionDispatchUncertainError } from './pageAct.js';
2
+ export class PageDialogInterruptedError extends Error {
3
+ pageActionFailureStage;
4
+ code = 'DIALOG_PENDING';
5
+ dialog;
6
+ details;
7
+ constructor(dialog, pageActionFailureStage = 'dispatch-uncertain') {
8
+ super(dialog
9
+ ? `DIALOG_PENDING: native ${dialog.type} dialog ${dialog.id}. Read it with page_snapshot, then use page_press with dialogId and Enter/Escape.`
10
+ : 'DIALOG_PENDING: a previous page operation was interrupted; observe the page before continuing.');
11
+ this.pageActionFailureStage = pageActionFailureStage;
12
+ this.name = 'PageDialogInterruptedError';
13
+ this.dialog = dialog;
14
+ this.details = { ...(dialog ? { dialog } : {}) };
15
+ }
16
+ }
17
+ /** Host-side state only: no page injection, implicit acceptance, or action replay. */
18
+ export class PageDialogState {
19
+ epoch = 0;
20
+ prefix = crypto.randomUUID();
21
+ current;
22
+ defaultPromptTruncated = false;
23
+ work = new Set();
24
+ listeners = new Set();
25
+ handling;
26
+ onIdle;
27
+ get pending() { return this.current !== undefined; }
28
+ get busy() { return this.work.size > 0 || this.handling !== undefined; }
29
+ info(maxChars = 240) {
30
+ if (!this.current)
31
+ return undefined;
32
+ const limit = Number.isFinite(maxChars) ? Math.max(0, Math.min(maxChars, 100000)) : 240;
33
+ const { message, defaultPrompt, ...rest } = this.current;
34
+ return {
35
+ ...rest,
36
+ message: message.slice(0, limit),
37
+ ...(defaultPrompt ? { defaultPrompt: defaultPrompt.slice(0, limit) } : {}),
38
+ ...(message.length > limit || (defaultPrompt?.length ?? 0) > limit ? { truncated: true } : {}),
39
+ };
40
+ }
41
+ opened(event) {
42
+ if (!['alert', 'confirm', 'prompt', 'beforeunload'].includes(String(event.type)))
43
+ return;
44
+ this.defaultPromptTruncated = typeof event.defaultPrompt === 'string' && event.defaultPrompt.length > 65536;
45
+ this.current = {
46
+ id: `${this.prefix}:${++this.epoch}`,
47
+ type: event.type,
48
+ message: typeof event.message === 'string' ? event.message.slice(0, 65536) : '',
49
+ ...(typeof event.defaultPrompt === 'string' && event.defaultPrompt
50
+ ? { defaultPrompt: event.defaultPrompt.slice(0, 65536) } : {}),
51
+ ...((typeof event.message === 'string' && event.message.length > 65536) ||
52
+ (typeof event.defaultPrompt === 'string' && event.defaultPrompt.length > 65536) ? { truncated: true } : {}),
53
+ };
54
+ for (const listener of this.listeners)
55
+ listener();
56
+ }
57
+ closed() {
58
+ this.current = undefined;
59
+ this.notifyIdle();
60
+ }
61
+ invalidate() {
62
+ this.epoch += 1;
63
+ this.current = undefined;
64
+ for (const listener of this.listeners)
65
+ listener();
66
+ this.notifyIdle();
67
+ }
68
+ assertCanDispatch() {
69
+ if (this.pending || [...this.work].some(entry => entry.epoch !== this.epoch)) {
70
+ throw new PageDialogInterruptedError(this.info(), 'precondition');
71
+ }
72
+ }
73
+ /** Register the actual handler, including its finally; never retain a raw browser Promise here. */
74
+ execute(work, interrupt = true) {
75
+ this.assertCanDispatch();
76
+ const entry = { epoch: this.epoch };
77
+ this.work.add(entry);
78
+ let started = false;
79
+ const release = () => {
80
+ this.work.delete(entry);
81
+ this.notifyIdle();
82
+ };
83
+ const start = () => {
84
+ started = true;
85
+ return Promise.resolve().then(() => {
86
+ this.assertCanDispatch();
87
+ return work();
88
+ }).finally(release);
89
+ };
90
+ return (interrupt ? this.race(start) : start()).finally(() => {
91
+ if (!started)
92
+ release();
93
+ });
94
+ }
95
+ /** Every real eval/input dispatch must pass here, so a late ack cannot resume its handler. */
96
+ command(work) {
97
+ this.assertCanDispatch();
98
+ return this.race(work);
99
+ }
100
+ race(work) {
101
+ if (this.pending)
102
+ return Promise.reject(new PageDialogInterruptedError(this.info()));
103
+ const epoch = this.epoch;
104
+ return new Promise((resolve, reject) => {
105
+ const changed = () => {
106
+ if (epoch === this.epoch)
107
+ return;
108
+ this.listeners.delete(changed);
109
+ reject(new PageDialogInterruptedError(this.info()));
110
+ };
111
+ this.listeners.add(changed);
112
+ let result;
113
+ try {
114
+ result = work();
115
+ }
116
+ catch (error) {
117
+ this.listeners.delete(changed);
118
+ reject(error);
119
+ return;
120
+ }
121
+ Promise.resolve(result).then(value => {
122
+ this.listeners.delete(changed);
123
+ resolve(value);
124
+ }, error => {
125
+ this.listeners.delete(changed);
126
+ reject(error);
127
+ });
128
+ });
129
+ }
130
+ async press(input, send) {
131
+ const dialog = this.current;
132
+ if (!input.dialogId || dialog?.id !== input.dialogId)
133
+ throw new Error('page_press: dialogId is stale or no native dialog is open. Run page_snapshot.');
134
+ if (this.handling)
135
+ throw new Error('page_press: this native dialog is already being handled.');
136
+ if (!['Enter', 'Escape'].includes(input.key) || input.selector !== undefined || input.modifiers?.length || (input.pressMode && input.pressMode !== 'auto')) {
137
+ throw new Error('page_press: dialogId requires Enter/Escape without selector, modifiers, or an explicit pressMode.');
138
+ }
139
+ if (input.promptText !== undefined && (typeof input.promptText !== 'string' || dialog.type !== 'prompt' || input.key !== 'Enter')) {
140
+ throw new Error('page_press: promptText is only valid when accepting a native prompt.');
141
+ }
142
+ const acceptsPrompt = dialog.type === 'prompt' && input.key === 'Enter';
143
+ if (acceptsPrompt && input.promptText === undefined && this.defaultPromptTruncated) {
144
+ throw new Error('page_press: the native prompt default was truncated. Specify promptText explicitly.');
145
+ }
146
+ this.handling = dialog.id;
147
+ try {
148
+ // No await between the last ID check and send. CDP itself has no atomic dialog-ID CAS.
149
+ await send({ accept: input.key === 'Enter', ...(acceptsPrompt ? { promptText: input.promptText ?? dialog.defaultPrompt ?? '' } : {}) });
150
+ return { pressed: true, key: input.key, dispatchMode: 'native-dialog' };
151
+ }
152
+ catch (error) {
153
+ throw new PageActionDispatchUncertainError('page_press: native dialog handling was not acknowledged. Read page_snapshot before trying again.', { cause: error });
154
+ }
155
+ finally {
156
+ this.handling = undefined;
157
+ // Closed/Opening events own current state; an ack for A must not clear a new dialog B.
158
+ this.notifyIdle();
159
+ }
160
+ }
161
+ notifyIdle() {
162
+ if (!this.pending && !this.busy)
163
+ this.onIdle?.();
164
+ }
165
+ }
166
+ export function pageDialogSnapshot(dialogs, mode = 'full', maxChars = 25000) {
167
+ const dialog = dialogs.info(maxChars);
168
+ return dialog ? {
169
+ snapshot: `[native ${dialog.type} dialog]`,
170
+ mode,
171
+ elementCount: 0,
172
+ outputNodeCount: 0,
173
+ dialog,
174
+ } : undefined;
175
+ }
@@ -28,9 +28,7 @@ export function isPotentialNavigationRequest(resourceType, url = '') {
28
28
  const normalizedType = resourceType.toLowerCase();
29
29
  if (normalizedType === 'xhr' || normalizedType === 'fetch')
30
30
  return true;
31
- if (normalizedType !== 'other')
32
- return false;
33
- return /(?:\/h5\/(?:mtop|mopen)|\/mtop\.|api-proxy)/i.test(url);
31
+ return false;
34
32
  }
35
33
  export function resolveNavigationWaitOptions(options = {}) {
36
34
  const timeoutMs = Math.min(Math.max(options.navigationTimeoutMs ?? 15000, 1000), 30000);
@@ -171,7 +169,7 @@ export function installPageObserverInPage(maxBatches = 50) {
171
169
  const escapeAttribute = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
172
170
  const isInternal = (input) => {
173
171
  const element = input?.nodeType === Node.ELEMENT_NODE ? input : (input?.parentElement ?? null);
174
- return Boolean(element?.closest('[data-mearl-overlay], #__mtop_click_style'));
172
+ return Boolean(element?.closest('[data-mearl-overlay], #__mearl_click_style'));
175
173
  };
176
174
  const isVisible = (element) => {
177
175
  if (element.closest('[hidden], [aria-hidden="true"]'))
@@ -810,7 +808,20 @@ export function installPageObserverInPage(maxBatches = 50) {
810
808
  const texts = [];
811
809
  let incompleteText = latestTexts.size > textLimit;
812
810
  for (const [element, effect] of latestTexts) {
813
- const current = readPlainText(element);
811
+ let current = readPlainText(element);
812
+ // Frameworks can replace a just-observed text leaf with an equivalent node.
813
+ // Only reuse its stable selector when the replacement is unique and passes
814
+ // the same visibility, clipping and text-completeness checks.
815
+ if (!element.isConnected) {
816
+ try {
817
+ const matches = document.querySelectorAll(effect.selector);
818
+ if (matches.length === 1)
819
+ current = readPlainText(matches[0]);
820
+ }
821
+ catch {
822
+ // Preserve the fallback when the recorded selector cannot be resolved.
823
+ }
824
+ }
814
825
  if (!current || current.selector !== effect.selector || current.text !== effect.text) {
815
826
  incompleteText = true;
816
827
  continue;
@@ -31,7 +31,7 @@ export interface PagePressDomTarget {
31
31
  export interface PagePressResultData {
32
32
  pressed: true;
33
33
  key: string;
34
- dispatchMode: PagePressDispatchMode;
34
+ dispatchMode: PagePressDispatchMode | 'native-dialog';
35
35
  fallbackReason?: PagePressFallbackReason;
36
36
  target?: PagePressDomTarget;
37
37
  keydownCanceled?: boolean;
@@ -0,0 +1,39 @@
1
+ import type { AppProfileOptions } from './appProfileDefinition.js';
2
+ import type { InjectedAppProfileConfig } from './app-profile-runtime/types.js';
3
+ import type { BrowserCapabilityCatalog } from './browserCapabilities.js';
4
+ import type { GetCapabilitiesParams, BrowserDevicePresetName } from './browserActionProtocol.js';
5
+ import type { ProviderEnhancementTarget } from './browserEnhancement.js';
6
+ export declare const PROVIDER_APP_RESOURCE_PROTOCOL_VERSION = 1;
7
+ export declare const MAX_PROVIDER_APP_RESOURCE_MESSAGE_BYTES: number;
8
+ export type ProviderAppResourceRequest = {
9
+ operation: 'catalog';
10
+ params?: GetCapabilitiesParams;
11
+ } | {
12
+ operation: 'resolve';
13
+ providerTabId: number;
14
+ profile: string;
15
+ options?: AppProfileOptions;
16
+ };
17
+ export interface ProviderAppResourceRevision {
18
+ capabilityRevision: string | null;
19
+ installationRevision: string | null;
20
+ }
21
+ export interface ResolvedProviderAppResources extends ProviderAppResourceRevision {
22
+ config: InjectedAppProfileConfig;
23
+ recommendedPreset: BrowserDevicePresetName;
24
+ runtime?: {
25
+ binding: {
26
+ id: string;
27
+ sha256: string;
28
+ };
29
+ source: string;
30
+ };
31
+ }
32
+ /** Code is returned only by explicit resolution, never with ordinary capability discovery. */
33
+ export type ProviderAppResourceResponse = (ProviderAppResourceRevision & {
34
+ catalog: BrowserCapabilityCatalog['apps'];
35
+ }) | (ResolvedProviderAppResources & {
36
+ target: Extract<ProviderEnhancementTarget, {
37
+ ready: true;
38
+ }>;
39
+ });
@@ -0,0 +1,2 @@
1
+ export const PROVIDER_APP_RESOURCE_PROTOCOL_VERSION = 1;
2
+ export const MAX_PROVIDER_APP_RESOURCE_MESSAGE_BYTES = 16 * 1024 * 1024;
@@ -6,7 +6,7 @@ export class DomainPermissionError extends Error {
6
6
  domain;
7
7
  code = 'DOMAIN_PERMISSION_REQUIRED';
8
8
  requestDispatched = false;
9
- constructor(action, domain, message = `${action}: Access to "${domain}" is not authorized. Call request_domain_permission with domain="${domain}" to request user authorization, then retry.`) {
9
+ constructor(action, domain, message = `${action}: Access to "${domain}" is not authorized. Call request_permission with domain="${domain}" to request user authorization, then retry.`) {
10
10
  super(message);
11
11
  this.action = action;
12
12
  this.domain = domain;
@@ -83,10 +83,11 @@ export function isDomainAuthorized(domain, allowedOrigins) {
83
83
  * wrappers whose individual steps are checked independently.
84
84
  */
85
85
  export const ACTIONS_SKIP_DOMAIN_CHECK = new Set([
86
- 'request_domain_permission',
86
+ 'request_permission',
87
87
  'get_browser_info',
88
+ 'get_capabilities',
88
89
  '__export_cookies',
89
- 'send_mtop_request',
90
+ 'send_protocol_request',
90
91
  'run_actions',
91
92
  'browser_release',
92
93
  '__control_cleanup',
@@ -0,0 +1,9 @@
1
+ /** Optional protocol annotations; the collector retains the original HTTP record. */
2
+ export interface RequestMetadata {
3
+ apiName: string;
4
+ namespace?: string;
5
+ isProxy: boolean;
6
+ proxyRequestUrl: string | null;
7
+ environment?: string;
8
+ }
9
+ export declare function parseRequestMetadata(value: unknown): RequestMetadata | null;
@@ -0,0 +1,8 @@
1
+ export function parseRequestMetadata(value) {
2
+ if (value === null || value === undefined)
3
+ return null;
4
+ const metadata = value;
5
+ if (typeof value !== 'object' || Array.isArray(value) || metadata.namespace !== undefined && (typeof metadata.namespace !== 'string' || metadata.namespace.length > 256) || typeof metadata.apiName !== 'string' || metadata.apiName.length > 4096 || typeof metadata.isProxy !== 'boolean' || metadata.proxyRequestUrl !== null && typeof metadata.proxyRequestUrl !== 'string' || metadata.environment !== undefined && (typeof metadata.environment !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,63}$/.test(metadata.environment)))
6
+ throw new Error('Invalid request metadata');
7
+ return { ...(metadata.namespace ? { namespace: metadata.namespace } : {}), apiName: metadata.apiName, isProxy: metadata.isProxy, proxyRequestUrl: metadata.proxyRequestUrl, ...(metadata.environment ? { environment: metadata.environment } : {}) };
8
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Request serialization utilities — pure computation, no environment dependencies.
3
3
  */
4
- import type { MtopRequest } from './types.js';
4
+ import type { CapturedRequest } from './types.js';
5
5
  export interface SerializeOptions {
6
6
  includeBody?: boolean;
7
7
  includeHeaders?: boolean;
@@ -10,4 +10,4 @@ export interface SerializeOptions {
10
10
  keysOnly?: boolean;
11
11
  }
12
12
  export declare function setValueByPath(obj: any, path: string, value: any): void;
13
- export declare function serializeRequest(request: MtopRequest, options?: SerializeOptions): any;
13
+ export declare function serializeRequest(request: CapturedRequest, options?: SerializeOptions): any;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Request serialization utilities — pure computation, no environment dependencies.
3
3
  */
4
+ import { getStructure } from './dataStructure.js';
4
5
  // ─── Path utilities ───────────────────────────────────────────────────────────
5
6
  function parsePath(path) {
6
7
  if (path.length > 1000)
@@ -67,35 +68,6 @@ export function setValueByPath(obj, path, value) {
67
68
  current[keys[keys.length - 1]] = value;
68
69
  }
69
70
  // ─── Structure helpers ────────────────────────────────────────────────────────
70
- function getStructure(data, maxDepth = 5, currentDepth = 0) {
71
- if (currentDepth >= maxDepth)
72
- return '<max depth reached>';
73
- if (data === null)
74
- return 'null';
75
- if (data === undefined)
76
- return 'undefined';
77
- const type = typeof data;
78
- if (type === 'string' || type === 'number' || type === 'boolean')
79
- return type;
80
- if (Array.isArray(data)) {
81
- if (data.length === 0)
82
- return 'array[0]';
83
- return {
84
- _type: `array[${data.length}]`,
85
- _sample: getStructure(data[0], maxDepth, currentDepth + 1),
86
- };
87
- }
88
- if (type === 'object') {
89
- const structure = {};
90
- for (const key in data) {
91
- if (Object.prototype.hasOwnProperty.call(data, key)) {
92
- structure[key] = getStructure(data[key], maxDepth, currentDepth + 1);
93
- }
94
- }
95
- return structure;
96
- }
97
- return type;
98
- }
99
71
  function truncateData(data, maxSize) {
100
72
  const str = JSON.stringify(data);
101
73
  if (str.length <= maxSize)