@mearl/native-host 2.7.1 → 2.7.2
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.
|
@@ -42,6 +42,23 @@ export interface AgentBaySessionLike {
|
|
|
42
42
|
errorMessage?: string;
|
|
43
43
|
}>;
|
|
44
44
|
};
|
|
45
|
+
fileSystem?: {
|
|
46
|
+
getFileTransferContextPath(): Promise<string | null>;
|
|
47
|
+
createDirectory(path: string): Promise<{
|
|
48
|
+
success?: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
errorMessage?: string;
|
|
51
|
+
}>;
|
|
52
|
+
uploadFile(localPath: string, remotePath: string, options?: {
|
|
53
|
+
wait?: boolean;
|
|
54
|
+
waitTimeout?: number;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
success?: boolean;
|
|
57
|
+
error?: string;
|
|
58
|
+
errorMessage?: string;
|
|
59
|
+
error_message?: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
45
62
|
delete(syncContext?: boolean): Promise<{
|
|
46
63
|
success: boolean;
|
|
47
64
|
errorMessage?: string;
|
|
@@ -130,6 +147,7 @@ export declare function normalizeDesktopUserAgent(value: unknown): string;
|
|
|
130
147
|
export declare function normalizeAgentBayAccessUrl(value: unknown): string | undefined;
|
|
131
148
|
export declare function hasAgentBayBrowser(selector: string): boolean;
|
|
132
149
|
export declare function listAgentBayBrowsers(): AgentBayBrowserSummary[];
|
|
150
|
+
export declare function getAgentBaySession(sessionId: string, dependencies?: Pick<AgentBayBrowserManagerDeps, 'createAgentBayClient' | 'resolveApiKey'>): Promise<AgentBaySessionLike>;
|
|
133
151
|
export declare function getAgentBayAccessUrl(sessionId: string, dependencies?: Pick<AgentBayBrowserManagerDeps, 'createAgentBayClient' | 'resolveApiKey'>): Promise<string>;
|
|
134
152
|
export declare function resolveAgentBayEndpoint(sessionId: string): Promise<string>;
|
|
135
153
|
export declare function keepAgentBaySessionAlive(sessionId: string): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AgentBaySessionLike } from './agentbay-browser.js';
|
|
2
|
+
interface AgentBayPageUploadDependencies {
|
|
3
|
+
getSession?: (sessionId: string) => Promise<AgentBaySessionLike>;
|
|
4
|
+
createTransferId?: () => string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Copies Native Host-local files into the active AgentBay session before CDP selects them.
|
|
8
|
+
* Each file gets an isolated directory so the browser-visible filename stays unchanged.
|
|
9
|
+
* Staged files remain session-scoped because Chromium may read them after the CDP call returns.
|
|
10
|
+
*/
|
|
11
|
+
export declare function stageAgentBayPageUploads(sessionId: string, action: string, data: any, dependencies?: AgentBayPageUploadDependencies): Promise<any>;
|
|
12
|
+
export {};
|
|
@@ -45,6 +45,7 @@ export interface CdpBuffers {
|
|
|
45
45
|
capture: CaptureSequenceState & {
|
|
46
46
|
droppedRequestsThroughSequence: number;
|
|
47
47
|
droppedLogsThroughSequence: number;
|
|
48
|
+
droppedEventsThroughSequence: number;
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
export declare function createBuffers(tabId?: number): CdpBuffers;
|
|
@@ -10,9 +10,8 @@ import type { CdpSession } from './cdp-session.js';
|
|
|
10
10
|
import { type ControlSource, type PageActAction, type RunActionsParams } from '@mearl/browser-core';
|
|
11
11
|
export declare function cdpRunActions(session: CdpSession, data: RunActionsParams, controlSource?: ControlSource): Promise<any>;
|
|
12
12
|
/**
|
|
13
|
-
* Runs a page interaction action with built-in observation
|
|
14
|
-
*
|
|
15
|
-
* `diagnostics: false` also disables the atomic console/network result.
|
|
13
|
+
* Runs a page interaction action with built-in observation. Diagnostics are
|
|
14
|
+
* collected only when the caller requests them.
|
|
16
15
|
* `run_actions` stays lightweight and never enters this wrapper.
|
|
17
16
|
*/
|
|
18
17
|
export declare function cdpObservedPageAction(session: CdpSession, action: PageActAction, data: any, controlSource?: ControlSource): Promise<any>;
|
package/dist/index.js
CHANGED
|
@@ -1002,6 +1002,83 @@ function parseTrackingRequest(url2, postData, options = {}) {
|
|
|
1002
1002
|
return armsEvent ? [armsEvent] : [];
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
+
// ../browser-core/dist/capture.js
|
|
1006
|
+
var CURSOR_PREFIX = "mearl-capture-v1";
|
|
1007
|
+
function createCaptureGeneration() {
|
|
1008
|
+
return crypto.randomUUID();
|
|
1009
|
+
}
|
|
1010
|
+
function encodeCaptureCursor(cursor) {
|
|
1011
|
+
return `${CURSOR_PREFIX}:${cursor.tabId}:${cursor.generation}:${cursor.scope}:${cursor.sequence}`;
|
|
1012
|
+
}
|
|
1013
|
+
function decodeCaptureCursor(value) {
|
|
1014
|
+
const match = value.match(/^mearl-capture-v1:(\d+):([^:]+):(all|logs|requests|events):(\d+)$/);
|
|
1015
|
+
if (!match)
|
|
1016
|
+
throw new Error("Invalid capture cursor");
|
|
1017
|
+
const tabId = Number(match[1]);
|
|
1018
|
+
const sequence = Number(match[4]);
|
|
1019
|
+
if (!Number.isSafeInteger(tabId) || !Number.isSafeInteger(sequence)) {
|
|
1020
|
+
throw new Error("Invalid capture cursor");
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
tabId,
|
|
1024
|
+
generation: match[2],
|
|
1025
|
+
scope: match[3],
|
|
1026
|
+
sequence
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function captureCheckpoint(state) {
|
|
1030
|
+
return encodeCaptureCursor({
|
|
1031
|
+
tabId: state.tabId,
|
|
1032
|
+
generation: state.generation,
|
|
1033
|
+
sequence: state.currentSequence,
|
|
1034
|
+
scope: "all"
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function nextCaptureSequence(state) {
|
|
1038
|
+
state.currentSequence += 1;
|
|
1039
|
+
return state.currentSequence;
|
|
1040
|
+
}
|
|
1041
|
+
function resolveAfterSequence(after, state, scope) {
|
|
1042
|
+
if (!after)
|
|
1043
|
+
return 0;
|
|
1044
|
+
const cursor = decodeCaptureCursor(after);
|
|
1045
|
+
if (cursor.tabId !== state.tabId) {
|
|
1046
|
+
throw new Error(`Capture cursor belongs to tab ${cursor.tabId}, not tab ${state.tabId}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (cursor.generation !== state.generation) {
|
|
1049
|
+
throw new Error("Capture cursor expired because the tab collector was restarted");
|
|
1050
|
+
}
|
|
1051
|
+
if (cursor.scope !== "all" && cursor.scope !== scope) {
|
|
1052
|
+
throw new Error(`Capture cursor is scoped to ${cursor.scope}, not ${scope}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (cursor.sequence > state.currentSequence) {
|
|
1055
|
+
throw new Error("Capture cursor is ahead of the current collector state");
|
|
1056
|
+
}
|
|
1057
|
+
return cursor.sequence;
|
|
1058
|
+
}
|
|
1059
|
+
function queryCapturedEntries(buffer, state, options, matches, droppedThroughSequence = 0, scope = "all") {
|
|
1060
|
+
const { after, since, now = Date.now() } = options;
|
|
1061
|
+
const limit = Math.max(1, Math.floor(options.limit ?? 100));
|
|
1062
|
+
const afterSequence = resolveAfterSequence(after, state, scope);
|
|
1063
|
+
const sinceTimestampMs = since === void 0 ? void 0 : now - since * 1e3;
|
|
1064
|
+
const matched = buffer.filter((entry) => entry.captureSequence > afterSequence && (sinceTimestampMs === void 0 || entry.timestampMs >= sinceTimestampMs) && matches(entry));
|
|
1065
|
+
const hasMore = matched.length > limit;
|
|
1066
|
+
const entries = after ? matched.slice(0, limit) : matched.slice(-limit);
|
|
1067
|
+
const nextSequence = after && hasMore && entries.length > 0 ? entries[entries.length - 1].captureSequence : state.currentSequence;
|
|
1068
|
+
return {
|
|
1069
|
+
entries,
|
|
1070
|
+
totalMatched: matched.length,
|
|
1071
|
+
nextCursor: encodeCaptureCursor({
|
|
1072
|
+
tabId: state.tabId,
|
|
1073
|
+
generation: state.generation,
|
|
1074
|
+
sequence: nextSequence,
|
|
1075
|
+
scope
|
|
1076
|
+
}),
|
|
1077
|
+
hasMore,
|
|
1078
|
+
gap: after !== void 0 && afterSequence < droppedThroughSequence
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1005
1082
|
// ../browser-core/dist/eventFilters.js
|
|
1006
1083
|
var TRACKING_CAPTURE_ARMED_HINT = "Event capture is active, but this tab has no captured history yet. Reload the current tab or repeat the triggering action, then call get_events again.";
|
|
1007
1084
|
function compileTextPattern(value) {
|
|
@@ -1030,15 +1107,31 @@ function compactEventUrl(value) {
|
|
|
1030
1107
|
}
|
|
1031
1108
|
}
|
|
1032
1109
|
function formatTrackingEvent(event, includeRaw = false) {
|
|
1110
|
+
const { captureSequence: _discardedCaptureSequence, ...publicEvent } = event;
|
|
1033
1111
|
if (includeRaw)
|
|
1034
|
-
return
|
|
1035
|
-
const { _raw: _discardedRaw, _context, url: url2, ...summary } =
|
|
1112
|
+
return publicEvent;
|
|
1113
|
+
const { _raw: _discardedRaw, _context, url: url2, ...summary } = publicEvent;
|
|
1036
1114
|
const compactUrl = compactEventUrl(url2 ?? _context.requestUrl);
|
|
1037
1115
|
return {
|
|
1038
1116
|
...summary,
|
|
1039
1117
|
...compactUrl ? { url: compactUrl } : {}
|
|
1040
1118
|
};
|
|
1041
1119
|
}
|
|
1120
|
+
function matchesTrackingEvent(event, options) {
|
|
1121
|
+
const { source = "all", event_type, filter } = options;
|
|
1122
|
+
if (source !== "all" && event.source !== source)
|
|
1123
|
+
return false;
|
|
1124
|
+
if (event_type && !matchesTextPattern(event.event_type, compileTextPattern(event_type))) {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
if (filter) {
|
|
1128
|
+
const pattern = compileTextPattern(filter);
|
|
1129
|
+
if (!matchesTextPattern(event.name, pattern) && !matchesTextPattern(event.url, pattern)) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1042
1135
|
function queryTrackingEvents(buffer, options = {}) {
|
|
1043
1136
|
const { source = "all", event_type, filter, since, limit = 20, now = Date.now() } = options;
|
|
1044
1137
|
let events = [...buffer].reverse();
|
|
@@ -1063,6 +1156,84 @@ function queryTrackingEvents(buffer, options = {}) {
|
|
|
1063
1156
|
totalInBuffer: buffer.length
|
|
1064
1157
|
};
|
|
1065
1158
|
}
|
|
1159
|
+
function appendCapturedTrackingBatch(buffer, events, maxSize) {
|
|
1160
|
+
if (events.length === 0)
|
|
1161
|
+
return [];
|
|
1162
|
+
const captureSequence = events[0].captureSequence;
|
|
1163
|
+
const insertionIndex = Number.isSafeInteger(captureSequence) ? buffer.findIndex((event) => Number.isSafeInteger(event.captureSequence) && event.captureSequence > captureSequence) : -1;
|
|
1164
|
+
buffer.splice(insertionIndex < 0 ? buffer.length : insertionIndex, 0, ...events);
|
|
1165
|
+
if (buffer.length <= maxSize)
|
|
1166
|
+
return [];
|
|
1167
|
+
let removeCount = buffer.length - maxSize;
|
|
1168
|
+
const boundarySequence = buffer[removeCount - 1]?.captureSequence;
|
|
1169
|
+
while (boundarySequence !== void 0 && buffer[removeCount]?.captureSequence === boundarySequence) {
|
|
1170
|
+
removeCount += 1;
|
|
1171
|
+
}
|
|
1172
|
+
return buffer.splice(0, removeCount);
|
|
1173
|
+
}
|
|
1174
|
+
function queryCapturedTrackingEvents(buffer, state, options = {}, droppedThroughSequence = 0) {
|
|
1175
|
+
const limit = Math.max(1, Math.floor(options.limit ?? 20));
|
|
1176
|
+
if (!options.after) {
|
|
1177
|
+
const snapshot = queryTrackingEvents(buffer, options);
|
|
1178
|
+
return {
|
|
1179
|
+
...snapshot,
|
|
1180
|
+
nextCursor: encodeCaptureCursor({
|
|
1181
|
+
tabId: state.tabId,
|
|
1182
|
+
generation: state.generation,
|
|
1183
|
+
sequence: state.currentSequence,
|
|
1184
|
+
scope: "events"
|
|
1185
|
+
}),
|
|
1186
|
+
hasMore: snapshot.totalMatched > limit,
|
|
1187
|
+
gap: false
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
const sinceTimestampMs = options.since === void 0 ? void 0 : (options.now ?? Date.now()) - options.since * 1e3;
|
|
1191
|
+
const batchesBySequence = /* @__PURE__ */ new Map();
|
|
1192
|
+
for (const event of buffer) {
|
|
1193
|
+
if (!Number.isSafeInteger(event.captureSequence))
|
|
1194
|
+
continue;
|
|
1195
|
+
if (sinceTimestampMs !== void 0 && event.batchTimestampMs < sinceTimestampMs)
|
|
1196
|
+
continue;
|
|
1197
|
+
if (!matchesTrackingEvent(event, options))
|
|
1198
|
+
continue;
|
|
1199
|
+
const captureSequence = event.captureSequence;
|
|
1200
|
+
const batch = batchesBySequence.get(captureSequence);
|
|
1201
|
+
if (batch)
|
|
1202
|
+
batch.events.push(event);
|
|
1203
|
+
else {
|
|
1204
|
+
batchesBySequence.set(captureSequence, {
|
|
1205
|
+
captureSequence,
|
|
1206
|
+
timestampMs: event.batchTimestampMs,
|
|
1207
|
+
events: [event]
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const batches = [...batchesBySequence.values()].sort((left, right) => left.captureSequence - right.captureSequence);
|
|
1212
|
+
const captured = queryCapturedEntries(batches, state, { after: options.after, limit: Number.MAX_SAFE_INTEGER }, () => true, droppedThroughSequence, "events");
|
|
1213
|
+
const selected = [];
|
|
1214
|
+
let selectedCount = 0;
|
|
1215
|
+
for (const batch of captured.entries) {
|
|
1216
|
+
if (selected.length > 0 && selectedCount + batch.events.length > limit)
|
|
1217
|
+
break;
|
|
1218
|
+
selected.push(batch);
|
|
1219
|
+
selectedCount += batch.events.length;
|
|
1220
|
+
}
|
|
1221
|
+
const hasMore = selected.length < captured.entries.length;
|
|
1222
|
+
const nextSequence = hasMore ? selected[selected.length - 1].captureSequence : state.currentSequence;
|
|
1223
|
+
return {
|
|
1224
|
+
events: selected.flatMap((batch) => batch.events),
|
|
1225
|
+
totalMatched: captured.entries.reduce((total, batch) => total + batch.events.length, 0),
|
|
1226
|
+
totalInBuffer: buffer.length,
|
|
1227
|
+
nextCursor: encodeCaptureCursor({
|
|
1228
|
+
tabId: state.tabId,
|
|
1229
|
+
generation: state.generation,
|
|
1230
|
+
sequence: nextSequence,
|
|
1231
|
+
scope: "events"
|
|
1232
|
+
}),
|
|
1233
|
+
hasMore,
|
|
1234
|
+
gap: captured.gap
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1066
1237
|
|
|
1067
1238
|
// ../browser-core/dist/requestSerializer.js
|
|
1068
1239
|
function parsePath(path15) {
|
|
@@ -3578,6 +3749,7 @@ var PAGE_ACT_ACTIONS = [
|
|
|
3578
3749
|
"page_eval"
|
|
3579
3750
|
];
|
|
3580
3751
|
var BROWSER_COMMAND_ACTIONS = {
|
|
3752
|
+
tab_checkpoint: true,
|
|
3581
3753
|
capture_checkpoint: true,
|
|
3582
3754
|
get_requests: true,
|
|
3583
3755
|
get_logs: true,
|
|
@@ -3622,6 +3794,7 @@ var BROWSER_COMMAND_ACTIONS = {
|
|
|
3622
3794
|
run_actions: true
|
|
3623
3795
|
};
|
|
3624
3796
|
var EXTENSION_BROWSER_ACTIONS = {
|
|
3797
|
+
tab_checkpoint: true,
|
|
3625
3798
|
capture_checkpoint: true,
|
|
3626
3799
|
get_requests: true,
|
|
3627
3800
|
get_logs: true,
|
|
@@ -3674,22 +3847,53 @@ var DEFAULT_PAGE_DIAGNOSTICS = {
|
|
|
3674
3847
|
logs: true,
|
|
3675
3848
|
requests: true
|
|
3676
3849
|
};
|
|
3850
|
+
var PageActionPreconditionError = class extends Error {
|
|
3851
|
+
pageActionFailureStage = "precondition";
|
|
3852
|
+
constructor(message, options) {
|
|
3853
|
+
super(message, options);
|
|
3854
|
+
this.name = "PageActionPreconditionError";
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
function isPageActionPreconditionError(error48) {
|
|
3858
|
+
return typeof error48 === "object" && error48 !== null && error48.pageActionFailureStage === "precondition";
|
|
3859
|
+
}
|
|
3860
|
+
function asPageActionPreconditionError(error48) {
|
|
3861
|
+
if (isPageActionPreconditionError(error48))
|
|
3862
|
+
return error48;
|
|
3863
|
+
return new PageActionPreconditionError(error48 instanceof Error ? error48.message : String(error48), {
|
|
3864
|
+
cause: error48
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
function checkPageActionPrecondition(check2) {
|
|
3868
|
+
try {
|
|
3869
|
+
return check2();
|
|
3870
|
+
} catch (error48) {
|
|
3871
|
+
throw asPageActionPreconditionError(error48);
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
function describePageActionFailure(error48) {
|
|
3875
|
+
return {
|
|
3876
|
+
error: error48 instanceof Error ? error48.message : String(error48),
|
|
3877
|
+
...isPageActionPreconditionError(error48) ? { stage: error48.pageActionFailureStage } : {}
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3677
3880
|
function isPageActAction(action) {
|
|
3678
3881
|
return PAGE_ACT_ACTIONS.includes(action);
|
|
3679
3882
|
}
|
|
3680
3883
|
function shouldObservePageAction(action, data) {
|
|
3681
3884
|
if (!isPageActAction(action))
|
|
3682
3885
|
return false;
|
|
3683
|
-
const
|
|
3886
|
+
const diagnosticsSetting = data?.diagnostics;
|
|
3887
|
+
const diagnosticsEnabled = diagnosticsSetting === true || diagnosticsSetting !== null && typeof diagnosticsSetting === "object" && !Array.isArray(diagnosticsSetting);
|
|
3684
3888
|
if (action === "page_eval") {
|
|
3685
3889
|
return Boolean(diagnosticsEnabled || data?.observe && typeof data.observe === "object" && !Array.isArray(data.observe));
|
|
3686
3890
|
}
|
|
3687
|
-
return data?.observe !== false ||
|
|
3891
|
+
return data?.observe !== false || diagnosticsEnabled;
|
|
3688
3892
|
}
|
|
3689
3893
|
function splitObservedActionPayload(action, data) {
|
|
3690
3894
|
const { observe, diagnostics, ...actionData } = data ?? {};
|
|
3691
3895
|
const setting = diagnostics;
|
|
3692
|
-
const resolvedDiagnostics = setting === false ? void 0 : setting === true ? DEFAULT_PAGE_DIAGNOSTICS : setting && typeof setting === "object" && !Array.isArray(setting) ? setting :
|
|
3896
|
+
const resolvedDiagnostics = setting === false ? void 0 : setting === true ? DEFAULT_PAGE_DIAGNOSTICS : setting && typeof setting === "object" && !Array.isArray(setting) ? setting : void 0;
|
|
3693
3897
|
if (observe === false)
|
|
3694
3898
|
return { actionData, observe: false, diagnostics: resolvedDiagnostics };
|
|
3695
3899
|
if (action === "page_eval" && (!observe || typeof observe !== "object")) {
|
|
@@ -3752,11 +3956,25 @@ async function runPageAct(input, dependencies) {
|
|
|
3752
3956
|
sourceTabId
|
|
3753
3957
|
}).catch(() => null);
|
|
3754
3958
|
let actionResult;
|
|
3755
|
-
let
|
|
3959
|
+
let actionFailure;
|
|
3756
3960
|
try {
|
|
3757
3961
|
actionResult = await dependencies.executeAction(typedAction, actionData);
|
|
3758
3962
|
} catch (error48) {
|
|
3759
|
-
|
|
3963
|
+
actionFailure = describePageActionFailure(error48);
|
|
3964
|
+
}
|
|
3965
|
+
if (actionFailure?.stage === "precondition") {
|
|
3966
|
+
await Promise.allSettled([
|
|
3967
|
+
Promise.resolve().then(() => navigationObserver?.dispose()),
|
|
3968
|
+
Promise.resolve().then(() => dependencies.disposeObservation?.())
|
|
3969
|
+
]);
|
|
3970
|
+
return {
|
|
3971
|
+
action: {
|
|
3972
|
+
name: typedAction,
|
|
3973
|
+
success: false,
|
|
3974
|
+
error: actionFailure.error,
|
|
3975
|
+
stage: actionFailure.stage
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3760
3978
|
}
|
|
3761
3979
|
let observation;
|
|
3762
3980
|
try {
|
|
@@ -3765,13 +3983,13 @@ async function runPageAct(input, dependencies) {
|
|
|
3765
3983
|
observation = unavailableObservation(checkpoint, error48);
|
|
3766
3984
|
}
|
|
3767
3985
|
try {
|
|
3768
|
-
if (navigationObserver &&
|
|
3986
|
+
if (navigationObserver && actionFailure === void 0 && (navigationObserver.hasStarted() || shouldWaitForPotentialNavigation(typedAction, observation))) {
|
|
3769
3987
|
const didNavigate = navigationObserver.hasStarted() || await navigationObserver.waitForStart(navigationOptions.graceMs);
|
|
3770
3988
|
if (didNavigate) {
|
|
3771
3989
|
observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
|
|
3772
3990
|
}
|
|
3773
3991
|
}
|
|
3774
|
-
if (navigationObserver &&
|
|
3992
|
+
if (navigationObserver && actionFailure === void 0 && observation.mode !== "navigation" && navigationObserver.hasNetworkActivity()) {
|
|
3775
3993
|
const network = await navigationObserver.waitForNetworkIdle(navigationOptions.timeoutMs);
|
|
3776
3994
|
if (navigationObserver.hasStarted()) {
|
|
3777
3995
|
observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
|
|
@@ -3806,9 +4024,9 @@ async function runPageAct(input, dependencies) {
|
|
|
3806
4024
|
return {
|
|
3807
4025
|
action: {
|
|
3808
4026
|
name: typedAction,
|
|
3809
|
-
success:
|
|
4027
|
+
success: actionFailure === void 0,
|
|
3810
4028
|
...actionResult !== void 0 ? { data: actionResult } : {},
|
|
3811
|
-
...
|
|
4029
|
+
...actionFailure ? actionFailure : {}
|
|
3812
4030
|
},
|
|
3813
4031
|
observation: compactObservationResponse(observation, checkpoint)
|
|
3814
4032
|
};
|
|
@@ -4566,83 +4784,6 @@ ${SOURCE_BADGES[source]}
|
|
|
4566
4784
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
4567
4785
|
}
|
|
4568
4786
|
|
|
4569
|
-
// ../browser-core/dist/capture.js
|
|
4570
|
-
var CURSOR_PREFIX = "mearl-capture-v1";
|
|
4571
|
-
function createCaptureGeneration() {
|
|
4572
|
-
return crypto.randomUUID();
|
|
4573
|
-
}
|
|
4574
|
-
function encodeCaptureCursor(cursor) {
|
|
4575
|
-
return `${CURSOR_PREFIX}:${cursor.tabId}:${cursor.generation}:${cursor.scope}:${cursor.sequence}`;
|
|
4576
|
-
}
|
|
4577
|
-
function decodeCaptureCursor(value) {
|
|
4578
|
-
const match = value.match(/^mearl-capture-v1:(\d+):([^:]+):(all|logs|requests):(\d+)$/);
|
|
4579
|
-
if (!match)
|
|
4580
|
-
throw new Error("Invalid capture cursor");
|
|
4581
|
-
const tabId = Number(match[1]);
|
|
4582
|
-
const sequence = Number(match[4]);
|
|
4583
|
-
if (!Number.isSafeInteger(tabId) || !Number.isSafeInteger(sequence)) {
|
|
4584
|
-
throw new Error("Invalid capture cursor");
|
|
4585
|
-
}
|
|
4586
|
-
return {
|
|
4587
|
-
tabId,
|
|
4588
|
-
generation: match[2],
|
|
4589
|
-
scope: match[3],
|
|
4590
|
-
sequence
|
|
4591
|
-
};
|
|
4592
|
-
}
|
|
4593
|
-
function captureCheckpoint(state) {
|
|
4594
|
-
return encodeCaptureCursor({
|
|
4595
|
-
tabId: state.tabId,
|
|
4596
|
-
generation: state.generation,
|
|
4597
|
-
sequence: state.currentSequence,
|
|
4598
|
-
scope: "all"
|
|
4599
|
-
});
|
|
4600
|
-
}
|
|
4601
|
-
function nextCaptureSequence(state) {
|
|
4602
|
-
state.currentSequence += 1;
|
|
4603
|
-
return state.currentSequence;
|
|
4604
|
-
}
|
|
4605
|
-
function resolveAfterSequence(after, state, scope) {
|
|
4606
|
-
if (!after)
|
|
4607
|
-
return 0;
|
|
4608
|
-
const cursor = decodeCaptureCursor(after);
|
|
4609
|
-
if (cursor.tabId !== state.tabId) {
|
|
4610
|
-
throw new Error(`Capture cursor belongs to tab ${cursor.tabId}, not tab ${state.tabId}`);
|
|
4611
|
-
}
|
|
4612
|
-
if (cursor.generation !== state.generation) {
|
|
4613
|
-
throw new Error("Capture cursor expired because the tab collector was restarted");
|
|
4614
|
-
}
|
|
4615
|
-
if (cursor.scope !== "all" && cursor.scope !== scope) {
|
|
4616
|
-
throw new Error(`Capture cursor is scoped to ${cursor.scope}, not ${scope}`);
|
|
4617
|
-
}
|
|
4618
|
-
if (cursor.sequence > state.currentSequence) {
|
|
4619
|
-
throw new Error("Capture cursor is ahead of the current collector state");
|
|
4620
|
-
}
|
|
4621
|
-
return cursor.sequence;
|
|
4622
|
-
}
|
|
4623
|
-
function queryCapturedEntries(buffer, state, options, matches, droppedThroughSequence = 0, scope = "all") {
|
|
4624
|
-
const { after, since, now = Date.now() } = options;
|
|
4625
|
-
const limit = Math.max(1, Math.floor(options.limit ?? 100));
|
|
4626
|
-
const afterSequence = resolveAfterSequence(after, state, scope);
|
|
4627
|
-
const sinceTimestampMs = since === void 0 ? void 0 : now - since * 1e3;
|
|
4628
|
-
const matched = buffer.filter((entry) => entry.captureSequence > afterSequence && (sinceTimestampMs === void 0 || entry.timestampMs >= sinceTimestampMs) && matches(entry));
|
|
4629
|
-
const hasMore = matched.length > limit;
|
|
4630
|
-
const entries = after ? matched.slice(0, limit) : matched.slice(-limit);
|
|
4631
|
-
const nextSequence = after && hasMore && entries.length > 0 ? entries[entries.length - 1].captureSequence : state.currentSequence;
|
|
4632
|
-
return {
|
|
4633
|
-
entries,
|
|
4634
|
-
totalMatched: matched.length,
|
|
4635
|
-
nextCursor: encodeCaptureCursor({
|
|
4636
|
-
tabId: state.tabId,
|
|
4637
|
-
generation: state.generation,
|
|
4638
|
-
sequence: nextSequence,
|
|
4639
|
-
scope
|
|
4640
|
-
}),
|
|
4641
|
-
hasMore,
|
|
4642
|
-
gap: after !== void 0 && afterSequence < droppedThroughSequence
|
|
4643
|
-
};
|
|
4644
|
-
}
|
|
4645
|
-
|
|
4646
4787
|
// src/cdp-backend/domain-manager.ts
|
|
4647
4788
|
var enabledDomains = /* @__PURE__ */ new WeakMap();
|
|
4648
4789
|
function ensureCdpDomain(client2, domain2, params = {}) {
|
|
@@ -4678,7 +4819,8 @@ function createBuffers(tabId = 0) {
|
|
|
4678
4819
|
generation: createCaptureGeneration(),
|
|
4679
4820
|
currentSequence: 0,
|
|
4680
4821
|
droppedRequestsThroughSequence: 0,
|
|
4681
|
-
droppedLogsThroughSequence: 0
|
|
4822
|
+
droppedLogsThroughSequence: 0,
|
|
4823
|
+
droppedEventsThroughSequence: 0
|
|
4682
4824
|
}
|
|
4683
4825
|
};
|
|
4684
4826
|
}
|
|
@@ -4741,15 +4883,20 @@ function parsePostData(postData) {
|
|
|
4741
4883
|
}
|
|
4742
4884
|
}
|
|
4743
4885
|
}
|
|
4744
|
-
function appendTrackingEvents(buffers, url2, postData,
|
|
4886
|
+
function appendTrackingEvents(buffers, url2, postData, batchSequence, captureSequence) {
|
|
4745
4887
|
const events = parseTrackingRequest(url2, postData, {
|
|
4746
|
-
sequence,
|
|
4888
|
+
sequence: batchSequence,
|
|
4747
4889
|
now: Date.now()
|
|
4748
|
-
});
|
|
4890
|
+
}).map((event) => ({ ...event, captureSequence }));
|
|
4749
4891
|
if (events.length === 0) return false;
|
|
4750
|
-
buffers.events
|
|
4751
|
-
|
|
4752
|
-
|
|
4892
|
+
const removed = appendCapturedTrackingBatch(buffers.events, events, MAX_EVENTS);
|
|
4893
|
+
for (const event of removed) {
|
|
4894
|
+
if (event.captureSequence !== void 0) {
|
|
4895
|
+
buffers.capture.droppedEventsThroughSequence = Math.max(
|
|
4896
|
+
buffers.capture.droppedEventsThroughSequence,
|
|
4897
|
+
event.captureSequence
|
|
4898
|
+
);
|
|
4899
|
+
}
|
|
4753
4900
|
}
|
|
4754
4901
|
return true;
|
|
4755
4902
|
}
|
|
@@ -4787,10 +4934,19 @@ async function setupBufferListeners(client2, buffers) {
|
|
|
4787
4934
|
const url2 = params.request?.url ?? "";
|
|
4788
4935
|
if (!url2.startsWith("http")) return;
|
|
4789
4936
|
if (isTrackingUrl(url2)) {
|
|
4790
|
-
const
|
|
4791
|
-
const
|
|
4937
|
+
const batchSequence = ++buffers.requestCounter;
|
|
4938
|
+
const captureSequence2 = nextCaptureSequence(buffers.capture);
|
|
4939
|
+
const captured = appendTrackingEvents(
|
|
4940
|
+
buffers,
|
|
4941
|
+
url2,
|
|
4942
|
+
params.request?.postData,
|
|
4943
|
+
batchSequence,
|
|
4944
|
+
captureSequence2
|
|
4945
|
+
);
|
|
4792
4946
|
if (!captured && params.request?.hasPostData && !params.request?.postData) {
|
|
4793
|
-
client2.sendCommand("Network.getRequestPostData", { requestId: params.requestId }, 1e4).then(
|
|
4947
|
+
client2.sendCommand("Network.getRequestPostData", { requestId: params.requestId }, 1e4).then(
|
|
4948
|
+
(result) => appendTrackingEvents(buffers, url2, result?.postData, batchSequence, captureSequence2)
|
|
4949
|
+
).catch(() => {
|
|
4794
4950
|
});
|
|
4795
4951
|
}
|
|
4796
4952
|
return;
|
|
@@ -6590,17 +6746,27 @@ async function collectCdpPageDiagnostics(session, after, diagnostics) {
|
|
|
6590
6746
|
};
|
|
6591
6747
|
result.requests = await cdpGetRequests(session, { ...options, after });
|
|
6592
6748
|
}
|
|
6749
|
+
if (diagnostics.events) {
|
|
6750
|
+
const options = diagnostics.events === true ? { source: "all", limit: 100 } : { source: "all", limit: 100, ...diagnostics.events };
|
|
6751
|
+
result.events = await cdpGetEvents(session, { ...options, after });
|
|
6752
|
+
}
|
|
6593
6753
|
return result;
|
|
6594
6754
|
}
|
|
6595
6755
|
async function cdpGetEvents(session, data) {
|
|
6596
|
-
const { source = "all", event_type, since, limit = 20, filter, includeRaw = false } = data;
|
|
6597
|
-
const response =
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6756
|
+
const { source = "all", event_type, since, after, limit = 20, filter, includeRaw = false } = data;
|
|
6757
|
+
const response = queryCapturedTrackingEvents(
|
|
6758
|
+
session.buffers.events,
|
|
6759
|
+
session.buffers.capture,
|
|
6760
|
+
{
|
|
6761
|
+
source,
|
|
6762
|
+
event_type,
|
|
6763
|
+
since,
|
|
6764
|
+
after,
|
|
6765
|
+
limit,
|
|
6766
|
+
filter
|
|
6767
|
+
},
|
|
6768
|
+
session.buffers.capture.droppedEventsThroughSequence
|
|
6769
|
+
);
|
|
6604
6770
|
const captureStatus = response.totalInBuffer === 0 ? "armed" : "active";
|
|
6605
6771
|
const hint = response.events.length === 0 && captureStatus === "armed" ? TRACKING_CAPTURE_ARMED_HINT : void 0;
|
|
6606
6772
|
return {
|
|
@@ -6612,12 +6778,17 @@ async function cdpGetEvents(session, data) {
|
|
|
6612
6778
|
captureStatus,
|
|
6613
6779
|
captureSource: "cdp",
|
|
6614
6780
|
includeRaw,
|
|
6781
|
+
after,
|
|
6782
|
+
nextCursor: response.nextCursor,
|
|
6783
|
+
hasMore: response.hasMore,
|
|
6784
|
+
gap: response.gap,
|
|
6615
6785
|
hint
|
|
6616
6786
|
},
|
|
6617
6787
|
events: response.events.map((event) => formatTrackingEvent(event, includeRaw))
|
|
6618
6788
|
};
|
|
6619
6789
|
}
|
|
6620
6790
|
registerActions([
|
|
6791
|
+
{ name: "tab_checkpoint", session: "page", handler: cdpCaptureCheckpoint },
|
|
6621
6792
|
{ name: "capture_checkpoint", session: "page", handler: cdpCaptureCheckpoint },
|
|
6622
6793
|
{ name: "get_requests", session: "page", handler: cdpGetRequests },
|
|
6623
6794
|
{ name: "get_logs", session: "page", handler: cdpGetLogs },
|
|
@@ -6708,19 +6879,23 @@ async function cdpObservedPageAction(session, action, data, controlSource = "loc
|
|
|
6708
6879
|
if (!diagnostics) return dispatchCdpAction(session, action, actionData, controlSource);
|
|
6709
6880
|
const cursor = captureCheckpoint(pageSession.buffers.capture);
|
|
6710
6881
|
let actionResult;
|
|
6711
|
-
let
|
|
6882
|
+
let actionFailure;
|
|
6712
6883
|
try {
|
|
6713
6884
|
actionResult = await dispatchCdpAction(session, action, actionData, controlSource);
|
|
6714
6885
|
} catch (error48) {
|
|
6715
|
-
|
|
6886
|
+
actionFailure = describePageActionFailure(error48);
|
|
6716
6887
|
}
|
|
6717
|
-
|
|
6888
|
+
const result2 = {
|
|
6718
6889
|
action: {
|
|
6719
6890
|
name: action,
|
|
6720
|
-
success:
|
|
6891
|
+
success: actionFailure === void 0,
|
|
6721
6892
|
...actionResult !== void 0 ? { data: actionResult } : {},
|
|
6722
|
-
...
|
|
6723
|
-
}
|
|
6893
|
+
...actionFailure ? actionFailure : {}
|
|
6894
|
+
}
|
|
6895
|
+
};
|
|
6896
|
+
if (actionFailure?.stage === "precondition") return result2;
|
|
6897
|
+
return {
|
|
6898
|
+
...result2,
|
|
6724
6899
|
diagnostics: await collectCdpPageDiagnostics(pageSession, cursor, diagnostics)
|
|
6725
6900
|
};
|
|
6726
6901
|
}
|
|
@@ -6744,7 +6919,7 @@ async function cdpObservedPageAction(session, action, data, controlSource = "loc
|
|
|
6744
6919
|
disposeObservation: () => disposePageObservation(pageSession)
|
|
6745
6920
|
}
|
|
6746
6921
|
);
|
|
6747
|
-
if (!diagnostics || !diagnosticsCursor) return result;
|
|
6922
|
+
if (!diagnostics || !diagnosticsCursor || result.action.stage === "precondition") return result;
|
|
6748
6923
|
return {
|
|
6749
6924
|
...result,
|
|
6750
6925
|
diagnostics: await collectCdpPageDiagnostics(pageSession, diagnosticsCursor, diagnostics)
|
|
@@ -8229,7 +8404,9 @@ var GET_DRAG_COORDS_FROM_NODE_SRC = getDragCoordsFromNode.toString();
|
|
|
8229
8404
|
function resolveSnapshotRef(session, ref) {
|
|
8230
8405
|
const entry = resolvePageRef(session, ref);
|
|
8231
8406
|
if (!entry) {
|
|
8232
|
-
throw new
|
|
8407
|
+
throw new PageActionPreconditionError(
|
|
8408
|
+
`Ref ${ref} not found. Run page_snapshot first to get fresh refs.`
|
|
8409
|
+
);
|
|
8233
8410
|
}
|
|
8234
8411
|
return entry;
|
|
8235
8412
|
}
|
|
@@ -8264,7 +8441,7 @@ async function clickBySelectorJs(session, selector) {
|
|
|
8264
8441
|
});
|
|
8265
8442
|
const value = result.result?.value;
|
|
8266
8443
|
if (!value) throw new Error("click evaluation returned no value");
|
|
8267
|
-
if (value.error) throw new
|
|
8444
|
+
if (value.error) throw new PageActionPreconditionError(value.error);
|
|
8268
8445
|
return { ...value, dispatchMode: "dom" };
|
|
8269
8446
|
}
|
|
8270
8447
|
async function clickByBackendNodeJs(session, backendNodeId) {
|
|
@@ -8275,6 +8452,9 @@ async function clickByBackendNodeJs(session, backendNodeId) {
|
|
|
8275
8452
|
returnByValue: true
|
|
8276
8453
|
});
|
|
8277
8454
|
const value = result.result?.value;
|
|
8455
|
+
if (value === null) {
|
|
8456
|
+
throw new PageActionPreconditionError("Cannot resolve clickable element");
|
|
8457
|
+
}
|
|
8278
8458
|
if (!value) throw new Error("click evaluation returned no value");
|
|
8279
8459
|
return {
|
|
8280
8460
|
tag: value.tag,
|
|
@@ -8295,11 +8475,12 @@ async function clickByPointJs(session, point) {
|
|
|
8295
8475
|
returnByValue: true
|
|
8296
8476
|
});
|
|
8297
8477
|
const value = result.result?.value;
|
|
8298
|
-
if (
|
|
8299
|
-
throw new
|
|
8478
|
+
if (value === null) {
|
|
8479
|
+
throw new PageActionPreconditionError(
|
|
8300
8480
|
`page_click: no element found at point (${point.x}, ${point.y}) in the current viewport.`
|
|
8301
8481
|
);
|
|
8302
8482
|
}
|
|
8483
|
+
if (!value) throw new Error("click evaluation returned no value");
|
|
8303
8484
|
return {
|
|
8304
8485
|
tag: value.tag,
|
|
8305
8486
|
text: value.text,
|
|
@@ -8337,7 +8518,7 @@ async function getElementCoordsBySelector(session, selector) {
|
|
|
8337
8518
|
});
|
|
8338
8519
|
const value = result.result?.value;
|
|
8339
8520
|
if (!value) throw new Error("coord evaluation returned no value");
|
|
8340
|
-
if (value.error) throw new
|
|
8521
|
+
if (value.error) throw new PageActionPreconditionError(value.error);
|
|
8341
8522
|
return value;
|
|
8342
8523
|
}
|
|
8343
8524
|
async function getElementCoordsByBackendNodeId(session, backendNodeId) {
|
|
@@ -8348,13 +8529,16 @@ async function getElementCoordsByBackendNodeId(session, backendNodeId) {
|
|
|
8348
8529
|
returnByValue: true
|
|
8349
8530
|
});
|
|
8350
8531
|
const value = result.result?.value;
|
|
8351
|
-
if (
|
|
8352
|
-
throw new
|
|
8532
|
+
if (value === null) {
|
|
8533
|
+
throw new PageActionPreconditionError(
|
|
8534
|
+
`Cannot resolve clickable element for backendNodeId ${backendNodeId}`
|
|
8535
|
+
);
|
|
8353
8536
|
}
|
|
8537
|
+
if (!value) throw new Error("coord evaluation returned no value");
|
|
8354
8538
|
return value;
|
|
8355
8539
|
}
|
|
8356
8540
|
async function resolveScopeBackendNodeId(session, scope) {
|
|
8357
|
-
validatePageElementSelector(scope, "page_click scope");
|
|
8541
|
+
checkPageActionPrecondition(() => validatePageElementSelector(scope, "page_click scope"));
|
|
8358
8542
|
if (scope.startsWith("@e")) {
|
|
8359
8543
|
return resolveSnapshotRef(session, scope).backendNodeId;
|
|
8360
8544
|
}
|
|
@@ -8364,17 +8548,17 @@ async function resolveScopeBackendNodeId(session, scope) {
|
|
|
8364
8548
|
});
|
|
8365
8549
|
if (evaluated.exceptionDetails) {
|
|
8366
8550
|
const message = evaluated.exceptionDetails.exception?.description ?? evaluated.exceptionDetails.text ?? "invalid selector";
|
|
8367
|
-
throw new
|
|
8551
|
+
throw new PageActionPreconditionError(`page_click: invalid scope "${scope}": ${message}`);
|
|
8368
8552
|
}
|
|
8369
8553
|
const objectId = evaluated.result?.objectId;
|
|
8370
8554
|
if (!objectId || evaluated.result?.subtype === "null") {
|
|
8371
|
-
throw new
|
|
8555
|
+
throw new PageActionPreconditionError(`page_click: scope not found: ${scope}`);
|
|
8372
8556
|
}
|
|
8373
8557
|
try {
|
|
8374
8558
|
const described = await session.client.sendCommand("DOM.describeNode", { objectId });
|
|
8375
8559
|
const backendNodeId = described.node?.backendNodeId;
|
|
8376
8560
|
if (backendNodeId === void 0) {
|
|
8377
|
-
throw new
|
|
8561
|
+
throw new PageActionPreconditionError(`page_click: cannot resolve scope: ${scope}`);
|
|
8378
8562
|
}
|
|
8379
8563
|
return backendNodeId;
|
|
8380
8564
|
} finally {
|
|
@@ -8391,21 +8575,28 @@ async function cdpPageClick(session, data) {
|
|
|
8391
8575
|
Boolean
|
|
8392
8576
|
);
|
|
8393
8577
|
if (clickMode !== void 0 && !["auto", "dom", "mouse", "touch"].includes(clickMode)) {
|
|
8394
|
-
throw new
|
|
8578
|
+
throw new PageActionPreconditionError(
|
|
8579
|
+
'page_click: "clickMode" must be one of auto, dom, mouse, touch'
|
|
8580
|
+
);
|
|
8395
8581
|
}
|
|
8396
8582
|
if (scope !== void 0 && !hasText) {
|
|
8397
|
-
throw new
|
|
8583
|
+
throw new PageActionPreconditionError(
|
|
8584
|
+
'page_click: "scope" is only supported with the "text" target'
|
|
8585
|
+
);
|
|
8398
8586
|
}
|
|
8399
8587
|
if (scope !== void 0) {
|
|
8400
|
-
validatePageElementSelector(scope, "page_click scope");
|
|
8588
|
+
checkPageActionPrecondition(() => validatePageElementSelector(scope, "page_click scope"));
|
|
8589
|
+
}
|
|
8590
|
+
if (hasSelector) {
|
|
8591
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_click"));
|
|
8401
8592
|
}
|
|
8402
8593
|
if (provided.length === 0) {
|
|
8403
|
-
throw new
|
|
8594
|
+
throw new PageActionPreconditionError(
|
|
8404
8595
|
'page_click: provide exactly one of "selector", "text", or "point". For text matching, pass "text" (and optionally "role"/"exact"). For coordinate clicks, pass "point": { x, y } in viewport pixels.'
|
|
8405
8596
|
);
|
|
8406
8597
|
}
|
|
8407
8598
|
if (provided.length > 1) {
|
|
8408
|
-
throw new
|
|
8599
|
+
throw new PageActionPreconditionError(
|
|
8409
8600
|
`page_click: provide exactly one of "selector", "text", or "point" — got ${provided.length} (${provided.join(", ")}).`
|
|
8410
8601
|
);
|
|
8411
8602
|
}
|
|
@@ -8483,13 +8674,13 @@ async function cdpPageClick(session, data) {
|
|
|
8483
8674
|
if (hits.length === 0) {
|
|
8484
8675
|
const exactSuffix = exact === false ? " (substring match)" : "";
|
|
8485
8676
|
const roleSuffix = role ? ` with role "${role}"` : "";
|
|
8486
|
-
throw new
|
|
8677
|
+
throw new PageActionPreconditionError(
|
|
8487
8678
|
`page_click: text "${text}"${roleSuffix}${exactSuffix}${scope ? ` in scope "${scope}"` : ""} not found. Run page_snapshot to inspect the page, or try exact: false for substring matching.`
|
|
8488
8679
|
);
|
|
8489
8680
|
}
|
|
8490
8681
|
if (hits.length > 1) {
|
|
8491
8682
|
const candidates = formatAXTextMatchCandidates(hits);
|
|
8492
|
-
throw new
|
|
8683
|
+
throw new PageActionPreconditionError(
|
|
8493
8684
|
`page_click: text "${text}"${scope ? ` in scope "${scope}"` : ""} matched ${hits.length} elements. Refine with "role" or "exact: true", or run page_snapshot and use an "@eN" ref:
|
|
8494
8685
|
${candidates}`
|
|
8495
8686
|
);
|
|
@@ -8519,7 +8710,7 @@ ${candidates}`
|
|
|
8519
8710
|
)
|
|
8520
8711
|
);
|
|
8521
8712
|
}
|
|
8522
|
-
if (!point) throw new
|
|
8713
|
+
if (!point) throw new PageActionPreconditionError("page_click point is required");
|
|
8523
8714
|
if (effectiveClickMode === "dom") {
|
|
8524
8715
|
const dispatchTarget = await clickByPointJs(session, point);
|
|
8525
8716
|
return finalize2(createPageClickResult({ kind: "point", point }, dispatchTarget));
|
|
@@ -8608,7 +8799,7 @@ async function resolveDragEndpoint(session, endpoint) {
|
|
|
8608
8799
|
};
|
|
8609
8800
|
}
|
|
8610
8801
|
async function cdpPageDrag(session, data) {
|
|
8611
|
-
const input = resolvePageDragInput(data);
|
|
8802
|
+
const input = checkPageActionPrecondition(() => resolvePageDragInput(data));
|
|
8612
8803
|
const visibilityResult = input.dragMode === "auto" ? await session.client.sendCommand("Runtime.evaluate", {
|
|
8613
8804
|
expression: "document.visibilityState",
|
|
8614
8805
|
returnByValue: true
|
|
@@ -8670,11 +8861,14 @@ async function cdpPageDrag(session, data) {
|
|
|
8670
8861
|
async function cdpPageType(session, data) {
|
|
8671
8862
|
const { selector, text, clearFirst = true } = data;
|
|
8672
8863
|
if (!selector || typeof selector !== "string") {
|
|
8673
|
-
throw new
|
|
8864
|
+
throw new PageActionPreconditionError(
|
|
8674
8865
|
`"selector" must be a non-empty string. Pass a native CSS selector (e.g. "[aria-label='提交']") or a snapshot ref like "@e3". Note: the field name is "selector", not "ref".`
|
|
8675
8866
|
);
|
|
8676
8867
|
}
|
|
8677
|
-
|
|
8868
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_type"));
|
|
8869
|
+
if (typeof text !== "string") {
|
|
8870
|
+
throw new PageActionPreconditionError('"text" must be a string.');
|
|
8871
|
+
}
|
|
8678
8872
|
if (selector.startsWith("@e")) {
|
|
8679
8873
|
return withSnapshotRef(session, selector, async (entry) => {
|
|
8680
8874
|
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
@@ -8688,7 +8882,7 @@ async function cdpPageType(session, data) {
|
|
|
8688
8882
|
});
|
|
8689
8883
|
const value2 = result.result?.value;
|
|
8690
8884
|
if (!value2) throw new Error("cdp_page_type: evaluation returned no value");
|
|
8691
|
-
if (value2.error) throw new
|
|
8885
|
+
if (value2.error) throw new PageActionPreconditionError(value2.error);
|
|
8692
8886
|
return { typed: true, tag: value2.tag };
|
|
8693
8887
|
});
|
|
8694
8888
|
}
|
|
@@ -8706,12 +8900,12 @@ async function cdpPageType(session, data) {
|
|
|
8706
8900
|
});
|
|
8707
8901
|
const value = r.result?.value;
|
|
8708
8902
|
if (!value) throw new Error("cdp_page_type: evaluation returned no value");
|
|
8709
|
-
if (value.error) throw new
|
|
8710
|
-
if (!value.typed) throw new
|
|
8903
|
+
if (value.error) throw new PageActionPreconditionError(value.error);
|
|
8904
|
+
if (!value.typed) throw new PageActionPreconditionError("cdp_page_type: failed to type");
|
|
8711
8905
|
return { typed: true, tag: value.tag };
|
|
8712
8906
|
}
|
|
8713
8907
|
async function cdpPageScroll(session, data) {
|
|
8714
|
-
const input = resolvePageScrollInput(data ?? {});
|
|
8908
|
+
const input = checkPageActionPrecondition(() => resolvePageScrollInput(data ?? {}));
|
|
8715
8909
|
if (input.selector?.startsWith("@e")) {
|
|
8716
8910
|
return withSnapshotRef(session, input.selector, async (entry) => {
|
|
8717
8911
|
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
@@ -8749,7 +8943,7 @@ async function cdpPageScroll(session, data) {
|
|
|
8749
8943
|
async function cdpPageEval(session, data) {
|
|
8750
8944
|
const { expression, awaitPromise = true } = data;
|
|
8751
8945
|
if (!expression || typeof expression !== "string") {
|
|
8752
|
-
throw new
|
|
8946
|
+
throw new PageActionPreconditionError("Invalid expression");
|
|
8753
8947
|
}
|
|
8754
8948
|
const result = await session.client.sendCommand("Runtime.evaluate", {
|
|
8755
8949
|
expression,
|
|
@@ -8765,7 +8959,7 @@ async function cdpPageEval(session, data) {
|
|
|
8765
8959
|
}
|
|
8766
8960
|
async function cdpPagePress(session, data) {
|
|
8767
8961
|
const { key } = data;
|
|
8768
|
-
if (!key || typeof key !== "string") throw new
|
|
8962
|
+
if (!key || typeof key !== "string") throw new PageActionPreconditionError("Invalid key");
|
|
8769
8963
|
await session.client.sendCommand("Input.dispatchKeyEvent", {
|
|
8770
8964
|
type: "keyDown",
|
|
8771
8965
|
key
|
|
@@ -8883,10 +9077,15 @@ async function waitForHistoryIndex(session, targetIndex, timeoutMs) {
|
|
|
8883
9077
|
async function cdpPageUpload(session, data) {
|
|
8884
9078
|
const { selector, filePaths } = data ?? {};
|
|
8885
9079
|
if (typeof selector !== "string" || selector.length === 0) {
|
|
8886
|
-
throw new
|
|
9080
|
+
throw new PageActionPreconditionError(
|
|
9081
|
+
'page_upload: "selector" is required (CSS selector or @eN snapshot ref)'
|
|
9082
|
+
);
|
|
8887
9083
|
}
|
|
9084
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_upload"));
|
|
8888
9085
|
if (!Array.isArray(filePaths) || filePaths.length === 0 || filePaths.some((filePath) => typeof filePath !== "string" || filePath.length === 0)) {
|
|
8889
|
-
throw new
|
|
9086
|
+
throw new PageActionPreconditionError(
|
|
9087
|
+
"Invalid filePaths: must be a non-empty array of file path strings"
|
|
9088
|
+
);
|
|
8890
9089
|
}
|
|
8891
9090
|
let described;
|
|
8892
9091
|
if (selector.startsWith("@e")) {
|
|
@@ -8903,16 +9102,20 @@ async function cdpPageUpload(session, data) {
|
|
|
8903
9102
|
nodeId: documentResult.root?.nodeId,
|
|
8904
9103
|
selector
|
|
8905
9104
|
});
|
|
8906
|
-
if (!queryResult.nodeId)
|
|
9105
|
+
if (!queryResult.nodeId) {
|
|
9106
|
+
throw new PageActionPreconditionError(`Element not found: ${selector}`);
|
|
9107
|
+
}
|
|
8907
9108
|
described = await session.client.sendCommand("DOM.describeNode", {
|
|
8908
9109
|
nodeId: queryResult.nodeId
|
|
8909
9110
|
});
|
|
8910
9111
|
}
|
|
8911
9112
|
const backendNodeId = described.node?.backendNodeId;
|
|
8912
9113
|
const tag = String(described.node?.nodeName ?? "").toLowerCase();
|
|
8913
|
-
if (backendNodeId === void 0)
|
|
9114
|
+
if (backendNodeId === void 0) {
|
|
9115
|
+
throw new PageActionPreconditionError(`Cannot resolve file input: ${selector}`);
|
|
9116
|
+
}
|
|
8914
9117
|
if (tag !== "input") {
|
|
8915
|
-
throw new
|
|
9118
|
+
throw new PageActionPreconditionError(
|
|
8916
9119
|
`Element <${tag || "unknown"}> is not a file input. selector must point to an <input type="file"> element.`
|
|
8917
9120
|
);
|
|
8918
9121
|
}
|
|
@@ -8971,8 +9174,11 @@ async function cdpSetDeviceEmulation(session, data) {
|
|
|
8971
9174
|
async function cdpPageHover(session, data) {
|
|
8972
9175
|
const { selector } = data ?? {};
|
|
8973
9176
|
if (typeof selector !== "string" || selector.length === 0) {
|
|
8974
|
-
throw new
|
|
9177
|
+
throw new PageActionPreconditionError(
|
|
9178
|
+
'page_hover: "selector" is required (CSS selector or @eN snapshot ref)'
|
|
9179
|
+
);
|
|
8975
9180
|
}
|
|
9181
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_hover"));
|
|
8976
9182
|
const coord = selector.startsWith("@e") ? await withSnapshotRef(
|
|
8977
9183
|
session,
|
|
8978
9184
|
selector,
|
|
@@ -10239,6 +10445,11 @@ async function requireSessionFromClient(agentBay, sessionId) {
|
|
|
10239
10445
|
}
|
|
10240
10446
|
return result.session;
|
|
10241
10447
|
}
|
|
10448
|
+
async function getAgentBaySession(sessionId, dependencies = {}) {
|
|
10449
|
+
const apiKey = (dependencies.resolveApiKey ?? resolveAgentBayApiKey)();
|
|
10450
|
+
const agentBay = dependencies.createAgentBayClient ? await dependencies.createAgentBayClient(apiKey) : await createClient(apiKey);
|
|
10451
|
+
return requireSessionFromClient(agentBay, sessionId);
|
|
10452
|
+
}
|
|
10242
10453
|
async function getAgentBayAccessUrl(sessionId, dependencies = {}) {
|
|
10243
10454
|
const apiKey = (dependencies.resolveApiKey ?? resolveAgentBayApiKey)();
|
|
10244
10455
|
const agentBay = dependencies.createAgentBayClient ? await dependencies.createAgentBayClient(apiKey) : await createClient(apiKey);
|
|
@@ -10592,6 +10803,83 @@ async function closeAgentBayBrowser(selector, options = {}) {
|
|
|
10592
10803
|
}
|
|
10593
10804
|
}
|
|
10594
10805
|
|
|
10806
|
+
// src/agentbay-file-upload.ts
|
|
10807
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10808
|
+
import { basename as basename2, posix } from "node:path";
|
|
10809
|
+
var AGENTBAY_FILE_UPLOAD_WAIT_SECONDS = 120;
|
|
10810
|
+
function pageUploadTargets(action, data) {
|
|
10811
|
+
if (action === "page_upload") return [{ data }];
|
|
10812
|
+
if (action !== "run_actions" || !Array.isArray(data?.actions)) return [];
|
|
10813
|
+
return data.actions.flatMap(
|
|
10814
|
+
(step, index) => step?.action === "page_upload" ? [{ data: step.data, step: index }] : []
|
|
10815
|
+
);
|
|
10816
|
+
}
|
|
10817
|
+
function validateFilePaths(target) {
|
|
10818
|
+
const filePaths = target.data?.filePaths;
|
|
10819
|
+
if (!Array.isArray(filePaths) || filePaths.length === 0 || filePaths.some((filePath) => typeof filePath !== "string" || filePath.length === 0)) {
|
|
10820
|
+
const prefix = target.step === void 0 ? "" : `Step ${target.step}: `;
|
|
10821
|
+
throw new Error(`${prefix}Invalid filePaths: must be a non-empty array of file path strings`);
|
|
10822
|
+
}
|
|
10823
|
+
return filePaths;
|
|
10824
|
+
}
|
|
10825
|
+
function uploadError(result) {
|
|
10826
|
+
return result.errorMessage ?? result.error_message ?? result.error ?? "unknown error";
|
|
10827
|
+
}
|
|
10828
|
+
async function stageAgentBayPageUploads(sessionId, action, data, dependencies = {}) {
|
|
10829
|
+
const targets = pageUploadTargets(action, data);
|
|
10830
|
+
if (targets.length === 0) return data;
|
|
10831
|
+
const targetFilePaths = targets.map((target) => validateFilePaths(target));
|
|
10832
|
+
const session = await (dependencies.getSession ?? getAgentBaySession)(sessionId);
|
|
10833
|
+
const fileSystem = session.fileSystem;
|
|
10834
|
+
if (!fileSystem) {
|
|
10835
|
+
throw new Error("The installed AgentBay SDK does not support session file transfer");
|
|
10836
|
+
}
|
|
10837
|
+
const contextPath = await fileSystem.getFileTransferContextPath();
|
|
10838
|
+
if (!contextPath) {
|
|
10839
|
+
throw new Error(`AgentBay file transfer context is unavailable for session ${sessionId}`);
|
|
10840
|
+
}
|
|
10841
|
+
const createTransferId = dependencies.createTransferId ?? randomUUID4;
|
|
10842
|
+
const stagedPaths = await Promise.all(
|
|
10843
|
+
targetFilePaths.map(
|
|
10844
|
+
(filePaths) => Promise.all(
|
|
10845
|
+
filePaths.map(async (localPath) => {
|
|
10846
|
+
const remoteDirectory = posix.join(contextPath, createTransferId());
|
|
10847
|
+
const directoryResult = await fileSystem.createDirectory(remoteDirectory);
|
|
10848
|
+
if (directoryResult?.success === false) {
|
|
10849
|
+
throw new Error(
|
|
10850
|
+
`Failed to prepare AgentBay upload for ${basename2(localPath)}: ${uploadError(directoryResult)}`
|
|
10851
|
+
);
|
|
10852
|
+
}
|
|
10853
|
+
const remotePath = posix.join(remoteDirectory, basename2(localPath));
|
|
10854
|
+
const result = await fileSystem.uploadFile(localPath, remotePath, {
|
|
10855
|
+
wait: true,
|
|
10856
|
+
waitTimeout: AGENTBAY_FILE_UPLOAD_WAIT_SECONDS
|
|
10857
|
+
});
|
|
10858
|
+
if (result?.success !== true) {
|
|
10859
|
+
throw new Error(
|
|
10860
|
+
`Failed to upload ${basename2(localPath)} to AgentBay: ${uploadError(result)}`
|
|
10861
|
+
);
|
|
10862
|
+
}
|
|
10863
|
+
return remotePath;
|
|
10864
|
+
})
|
|
10865
|
+
)
|
|
10866
|
+
)
|
|
10867
|
+
);
|
|
10868
|
+
if (action === "page_upload") {
|
|
10869
|
+
return { ...data, filePaths: stagedPaths[0] };
|
|
10870
|
+
}
|
|
10871
|
+
const runActions = data;
|
|
10872
|
+
let targetIndex = 0;
|
|
10873
|
+
return {
|
|
10874
|
+
...runActions,
|
|
10875
|
+
actions: runActions.actions.map((step) => {
|
|
10876
|
+
if (step.action !== "page_upload") return step;
|
|
10877
|
+
const filePaths = stagedPaths[targetIndex++];
|
|
10878
|
+
return { ...step, data: { ...step.data, filePaths } };
|
|
10879
|
+
})
|
|
10880
|
+
};
|
|
10881
|
+
}
|
|
10882
|
+
|
|
10595
10883
|
// src/browser-registry.ts
|
|
10596
10884
|
var BROWSERS_SUBDIR = "browsers";
|
|
10597
10885
|
var STALE_RECORD_TTL_MS = 5 * 6e4;
|
|
@@ -10785,10 +11073,10 @@ function listBrowserInventory() {
|
|
|
10785
11073
|
import fs3 from "node:fs";
|
|
10786
11074
|
import path3 from "node:path";
|
|
10787
11075
|
import os2 from "node:os";
|
|
10788
|
-
import { randomUUID as
|
|
11076
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
10789
11077
|
|
|
10790
11078
|
// src/version.ts
|
|
10791
|
-
var NATIVE_HOST_VERSION = true ? "2.7.
|
|
11079
|
+
var NATIVE_HOST_VERSION = true ? "2.7.2" : "unknown";
|
|
10792
11080
|
|
|
10793
11081
|
// src/analytics.ts
|
|
10794
11082
|
var MEASUREMENT_ID = "G-W84LJDZ6PH";
|
|
@@ -10813,7 +11101,7 @@ function writeConfig(config2) {
|
|
|
10813
11101
|
}
|
|
10814
11102
|
}
|
|
10815
11103
|
var clientId = null;
|
|
10816
|
-
function createClientId(seed =
|
|
11104
|
+
function createClientId(seed = randomUUID5()) {
|
|
10817
11105
|
let first = 2166136261;
|
|
10818
11106
|
let second = 2246822519;
|
|
10819
11107
|
for (let index = 0; index < seed.length; index++) {
|
|
@@ -32061,11 +32349,6 @@ async function handleViaCdp(action, data, _timeoutMs, controlSource) {
|
|
|
32061
32349
|
`${action} requires the Mearl browser extension. Install it from https://chromewebstore.google.com/detail/mearl/aoehhjnofngknnjefamjbplchbolghkm`
|
|
32062
32350
|
);
|
|
32063
32351
|
}
|
|
32064
|
-
if (AGENTBAY_SESSION_ID && action === "page_upload") {
|
|
32065
|
-
throw new Error(
|
|
32066
|
-
"page_upload is not available for AgentBay CDP sessions because file paths resolve on the remote browser host."
|
|
32067
|
-
);
|
|
32068
|
-
}
|
|
32069
32352
|
const available = AGENTBAY_SESSION_ID ? await getCdpSession().getBrowserClient().then(
|
|
32070
32353
|
() => true,
|
|
32071
32354
|
() => false
|
|
@@ -32080,10 +32363,11 @@ async function handleViaCdp(action, data, _timeoutMs, controlSource) {
|
|
|
32080
32363
|
`Chrome DevTools remote debugging is not available on port ${CDP_PORT}. Enable it via chrome://inspect/#remote-debugging (Chrome 145+) or install the Mearl extension.`
|
|
32081
32364
|
);
|
|
32082
32365
|
}
|
|
32083
|
-
|
|
32084
|
-
|
|
32366
|
+
const actionData = AGENTBAY_SESSION_ID ? await stageAgentBayPageUploads(AGENTBAY_SESSION_ID, action, data) : data;
|
|
32367
|
+
if (shouldObservePageAction(action, actionData)) {
|
|
32368
|
+
return cdpObservedPageAction(getCdpSession(), action, actionData, controlSource);
|
|
32085
32369
|
}
|
|
32086
|
-
return dispatchCdpAction(getCdpSession(), action,
|
|
32370
|
+
return dispatchCdpAction(getCdpSession(), action, actionData, controlSource);
|
|
32087
32371
|
}
|
|
32088
32372
|
function onSocketConnection(socket) {
|
|
32089
32373
|
let buffer = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/native-host",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "Native Messaging Host for Mearl — bridges Chrome Extension and local socket server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/install.js",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"@types/ws": "^8.18.1",
|
|
38
38
|
"esbuild": "^0.25.2",
|
|
39
39
|
"typescript": "^5.8.3",
|
|
40
|
-
"@mearl/browser-core": "2.7.
|
|
41
|
-
"@mearl/daemon-core": "2.7.
|
|
42
|
-
"@mearl/setup": "2.7.
|
|
40
|
+
"@mearl/browser-core": "2.7.2",
|
|
41
|
+
"@mearl/daemon-core": "2.7.2",
|
|
42
|
+
"@mearl/setup": "2.7.2"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "rm -rf dist/ && node build.mjs",
|