@mearl/browser-core 3.0.0-beta.0 → 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.
- package/README.md +7 -5
- package/README.zh-CN.md +13 -0
- package/dist/agentChat.d.ts +10 -1
- package/dist/agentChat.js +8 -0
- package/dist/agentServiceProtocol.d.ts +23 -0
- package/dist/agentServiceProtocol.js +3 -0
- package/dist/agentSkills.d.ts +1 -1
- package/dist/app-profile-runtime/types.d.ts +10 -12
- package/dist/appProfileDefinition.d.ts +32 -14
- package/dist/appProfileDefinition.js +63 -69
- package/dist/axSnapshot.js +1 -1
- package/dist/boundedRequestBuffer.d.ts +4 -4
- package/dist/browserActionProtocol.d.ts +55 -45
- package/dist/browserActionProtocol.js +12 -15
- package/dist/browserCapabilities.d.ts +47 -0
- package/dist/browserCapabilities.js +63 -0
- package/dist/browserDiscovery.d.ts +3 -0
- package/dist/browserDiscovery.js +20 -0
- package/dist/browserEnhancement.d.ts +161 -0
- package/dist/browserEnhancement.js +80 -0
- package/dist/cookie.d.ts +6 -0
- package/dist/cookie.js +20 -5
- package/dist/dataStructure.d.ts +8 -0
- package/dist/dataStructure.js +58 -0
- package/dist/deviceEmulation.d.ts +6 -0
- package/dist/deviceEmulation.js +13 -0
- package/dist/deviceEnvironmentObservation.d.ts +48 -0
- package/dist/deviceEnvironmentObservation.js +77 -0
- package/dist/domClicking.d.ts +14 -0
- package/dist/domClicking.js +98 -10
- package/dist/domInspection.js +13 -1
- package/dist/extensionServiceProtocol.d.ts +7 -0
- package/dist/extensionServiceProtocol.js +8 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/nativeHostNames.d.ts +6 -0
- package/dist/nativeHostNames.js +5 -0
- package/dist/pageAct.d.ts +2 -0
- package/dist/pageAct.js +42 -10
- package/dist/pageDialog.d.ts +66 -0
- package/dist/pageDialog.js +175 -0
- package/dist/pageObserver.js +16 -5
- package/dist/pagePressing.d.ts +1 -1
- package/dist/pluginPageHost.js +2 -2
- package/dist/providerAppResources.d.ts +39 -0
- package/dist/providerAppResources.js +2 -0
- package/dist/proxyPermissions.js +3 -3
- package/dist/requestMetadata.d.ts +9 -0
- package/dist/requestMetadata.js +8 -0
- package/dist/requestSerializer.d.ts +2 -2
- package/dist/requestSerializer.js +1 -29
- package/dist/requestStreamCapture.d.ts +5 -3
- package/dist/requestStreamCapture.js +2 -0
- package/dist/runActions.js +1 -1
- package/dist/types.d.ts +4 -2
- package/package.json +6 -1
package/dist/domInspection.js
CHANGED
|
@@ -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
|
-
|
|
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';
|
|
@@ -56,3 +62,6 @@ export * from './responseText.js';
|
|
|
56
62
|
export * from './consumeSseResponse.js';
|
|
57
63
|
export { parseQueryString } from './queryString.js';
|
|
58
64
|
export * from './extensionVersion.js';
|
|
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';
|
|
@@ -55,3 +61,6 @@ export * from './responseText.js';
|
|
|
55
61
|
export * from './consumeSseResponse.js';
|
|
56
62
|
export { parseQueryString } from './queryString.js';
|
|
57
63
|
export * from './extensionVersion.js';
|
|
64
|
+
export { MEARL_NATIVE_HOST_NAMES } from './nativeHostNames.js';
|
|
65
|
+
export * from './dataStructure.js';
|
|
66
|
+
export * from './requestMetadata.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Native Messaging identifiers shared by the installer and extension. */
|
|
2
|
+
export declare const MEARL_NATIVE_HOST_NAMES: {
|
|
3
|
+
readonly skills: "ai.mearl.skills";
|
|
4
|
+
readonly mcp: "ai.mearl.mcp";
|
|
5
|
+
};
|
|
6
|
+
export type NativeHostIntegrationMode = keyof typeof MEARL_NATIVE_HOST_NAMES;
|
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
|
|
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
|
+
}
|
package/dist/pageObserver.js
CHANGED
|
@@ -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
|
-
|
|
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], #
|
|
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
|
-
|
|
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;
|
package/dist/pagePressing.d.ts
CHANGED
|
@@ -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;
|
package/dist/pluginPageHost.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
+
});
|