@mearl/browser-core 3.0.0-beta.1 → 3.0.0-beta.10

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 (54) hide show
  1. package/README.md +7 -5
  2. package/README.zh-CN.md +13 -0
  3. package/dist/agentChat.d.ts +10 -1
  4. package/dist/agentChat.js +8 -0
  5. package/dist/agentServiceProtocol.d.ts +23 -0
  6. package/dist/agentServiceProtocol.js +3 -0
  7. package/dist/agentSkills.d.ts +1 -1
  8. package/dist/app-profile-runtime/types.d.ts +10 -12
  9. package/dist/appProfileDefinition.d.ts +32 -14
  10. package/dist/appProfileDefinition.js +63 -69
  11. package/dist/axSnapshot.js +1 -1
  12. package/dist/boundedRequestBuffer.d.ts +4 -4
  13. package/dist/browserActionProtocol.d.ts +55 -45
  14. package/dist/browserActionProtocol.js +12 -15
  15. package/dist/browserCapabilities.d.ts +47 -0
  16. package/dist/browserCapabilities.js +63 -0
  17. package/dist/browserDiscovery.d.ts +3 -0
  18. package/dist/browserDiscovery.js +20 -0
  19. package/dist/browserEnhancement.d.ts +161 -0
  20. package/dist/browserEnhancement.js +80 -0
  21. package/dist/cookie.d.ts +6 -0
  22. package/dist/cookie.js +20 -5
  23. package/dist/dataStructure.d.ts +8 -0
  24. package/dist/dataStructure.js +58 -0
  25. package/dist/deviceEmulation.d.ts +6 -0
  26. package/dist/deviceEmulation.js +13 -0
  27. package/dist/deviceEnvironmentObservation.d.ts +48 -0
  28. package/dist/deviceEnvironmentObservation.js +77 -0
  29. package/dist/domClicking.d.ts +14 -0
  30. package/dist/domClicking.js +98 -10
  31. package/dist/domInspection.js +13 -1
  32. package/dist/extensionServiceProtocol.d.ts +7 -0
  33. package/dist/extensionServiceProtocol.js +8 -0
  34. package/dist/index.d.ts +8 -0
  35. package/dist/index.js +8 -0
  36. package/dist/pageAct.d.ts +2 -0
  37. package/dist/pageAct.js +42 -10
  38. package/dist/pageDialog.d.ts +66 -0
  39. package/dist/pageDialog.js +175 -0
  40. package/dist/pageObserver.js +16 -5
  41. package/dist/pagePressing.d.ts +1 -1
  42. package/dist/pluginPageHost.js +2 -2
  43. package/dist/providerAppResources.d.ts +39 -0
  44. package/dist/providerAppResources.js +2 -0
  45. package/dist/proxyPermissions.js +3 -3
  46. package/dist/requestMetadata.d.ts +9 -0
  47. package/dist/requestMetadata.js +8 -0
  48. package/dist/requestSerializer.d.ts +2 -2
  49. package/dist/requestSerializer.js +1 -29
  50. package/dist/requestStreamCapture.d.ts +5 -3
  51. package/dist/requestStreamCapture.js +2 -0
  52. package/dist/runActions.js +1 -1
  53. package/dist/types.d.ts +4 -2
  54. package/package.json +2 -2
@@ -211,9 +211,21 @@ export function inspectElementsInPage(selector, filter, styles, maxResults, inva
211
211
  let hit = null;
212
212
  try {
213
213
  hit = visible ? (documentRef.elementFromPoint?.(x, y) ?? null) : null;
214
+ const visited = new Set();
215
+ while (hit?.shadowRoot?.elementFromPoint && !visited.has(hit)) {
216
+ visited.add(hit);
217
+ const inner = hit.shadowRoot.elementFromPoint(x, y);
218
+ if (!inner || inner === hit)
219
+ break;
220
+ hit = inner;
221
+ }
214
222
  }
215
223
  catch { }
216
- const topmost = !!hit && (hit === element || element.contains?.(hit));
224
+ let composedHit = hit;
225
+ while (composedHit && composedHit !== element) {
226
+ composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
227
+ }
228
+ const topmost = !!hit && (!!composedHit || element.contains?.(hit));
217
229
  const target = hit
218
230
  ? {
219
231
  selector: selectorFor(hit),
@@ -1,5 +1,6 @@
1
1
  import type { BrowserAccessResult, BrowserActionMap, BrowserJsonValue, BrowserProviderListResult, BrowserProviderLaunchForm, ControlSessionInfo, CookieCopyScope, ManagedBrowserInfo, BrowserReleaseParams, BrowserReleaseResult, PageDownloadParams } from './browserActionProtocol.js';
2
2
  import { type AgentServiceActionMap } from './agentServiceProtocol.js';
3
+ import type { BrowserEnhancementRequest, BrowserEnhancementResponse } from './browserEnhancement.js';
3
4
  import { type ToolboxServiceActionMap, type ToolboxTaskRequest, type ToolboxTaskResponse } from './toolbox.js';
4
5
  export declare const EXTENSION_RPC_VERSION = 1;
5
6
  export type OperationRequest<M> = {
@@ -171,6 +172,10 @@ export type ExtensionUiServiceRequest<A extends ExtensionUiServiceAction> = Exte
171
172
  export type ExtensionUiServiceResponse<A extends ExtensionUiServiceAction, R> = A extends BrowserManagementAction ? BrowserManagementResponse<A, R> : A extends 'toolbox_task' ? R extends ToolboxTaskRequest ? ToolboxTaskResponse<R> : never : ExtensionUiServiceActionMap[A]['response'];
172
173
  export declare function isExtensionUiServiceAction(value: unknown): value is ExtensionUiServiceAction;
173
174
  export interface HostToExtensionActionMap {
175
+ __browser_enhancement: {
176
+ request: BrowserEnhancementRequest;
177
+ response: BrowserEnhancementResponse;
178
+ };
174
179
  __preset_installation: {
175
180
  request: Record<string, unknown>;
176
181
  response: unknown;
@@ -206,6 +211,7 @@ export interface HostToExtensionActionMap {
206
211
  __control_cleanup: {
207
212
  request: BrowserReleaseParams & {
208
213
  tabIds: number[];
214
+ returningToAgent?: boolean;
209
215
  };
210
216
  response: BrowserReleaseResult;
211
217
  };
@@ -249,6 +255,7 @@ export interface HostToExtensionActionMap {
249
255
  };
250
256
  }
251
257
  export declare const HOST_TO_EXTENSION_ACTIONS: {
258
+ readonly __browser_enhancement: true;
252
259
  readonly __preset_installation: true;
253
260
  readonly __preset_browser: true;
254
261
  readonly __export_cookies: true;
@@ -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,
@@ -89,6 +90,13 @@ export function getExtensionServiceTimeout(action, data = {}) {
89
90
  return 40000;
90
91
  if (action === 'agent_chat_pick_directory')
91
92
  return 300000;
93
+ if (action === 'agent_chat_install_provider')
94
+ return 420000;
95
+ if (action === 'agent_chat_login_qoder')
96
+ return 60000;
97
+ // Installation can prepare a plugin's runtime after the Skill manager returns.
98
+ if (action === 'agent_skills_install' || action === 'agent_skills_update')
99
+ return 600000;
92
100
  if (action.startsWith('agent_skills_'))
93
101
  return 130000;
94
102
  if ([
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;
@@ -302,7 +302,7 @@ export class PluginPageRuntimeHost {
302
302
  !isPluginPageInstanceId(instance.instanceId) ||
303
303
  !instance.host?.hostId ||
304
304
  !instance.host.epoch ||
305
- !/^[a-z][a-z0-9-]*$/.test(instance.plugin) ||
305
+ !/^[a-z][a-z0-9_-]*$/.test(instance.plugin) ||
306
306
  !data.runtime?.code ||
307
307
  !Array.isArray(data.runtime.matches))
308
308
  throw new Error('Invalid plugin page attachment');
@@ -614,7 +614,7 @@ export class PluginPageRuntimeHost {
614
614
  throw new Error('PLUGIN_PAGE_INVALIDATED: Page left its activation URL range');
615
615
  }
616
616
  async activation(data, preserveManual = false) {
617
- if (!/^[a-z][a-z0-9-]*$/.test(data.plugin))
617
+ if (!/^[a-z][a-z0-9_-]*$/.test(data.plugin))
618
618
  throw new Error('Invalid page activation plugin');
619
619
  if (data.enabled === undefined)
620
620
  return { activation: this.rules.get(data.plugin) ?? null };
@@ -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;