@mearl/native-host 2.7.0 → 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) {
|
|
@@ -2091,8 +2262,8 @@ function resolvePageDragInput(data) {
|
|
|
2091
2262
|
if (!data || typeof data !== "object") {
|
|
2092
2263
|
throw new Error("page_drag: payload is required");
|
|
2093
2264
|
}
|
|
2094
|
-
if (data.dragMode !== void 0 && !["auto", "mouse", "touch"].includes(data.dragMode)) {
|
|
2095
|
-
throw new Error('page_drag: "dragMode" must be one of auto, mouse, touch');
|
|
2265
|
+
if (data.dragMode !== void 0 && !["auto", "dom", "mouse", "touch"].includes(data.dragMode)) {
|
|
2266
|
+
throw new Error('page_drag: "dragMode" must be one of auto, dom, mouse, touch');
|
|
2096
2267
|
}
|
|
2097
2268
|
const durationMs = data.durationMs ?? PAGE_DRAG_DEFAULT_DURATION_MS;
|
|
2098
2269
|
if (!Number.isFinite(durationMs) || durationMs < 0 || durationMs > PAGE_DRAG_MAX_DURATION_MS) {
|
|
@@ -2105,6 +2276,16 @@ function resolvePageDragInput(data) {
|
|
|
2105
2276
|
durationMs: Math.round(durationMs)
|
|
2106
2277
|
};
|
|
2107
2278
|
}
|
|
2279
|
+
function resolvePageDragDispatchPolicy(dragMode, visibilityState, touchEmulationEnabled) {
|
|
2280
|
+
const requestedMode = dragMode ?? "auto";
|
|
2281
|
+
const pointerType = requestedMode === "mouse" || requestedMode === "touch" ? requestedMode : touchEmulationEnabled ? "touch" : "mouse";
|
|
2282
|
+
if (requestedMode === "dom")
|
|
2283
|
+
return { dispatchMode: "dom", pointerType };
|
|
2284
|
+
if (requestedMode === "auto" && visibilityState === "hidden") {
|
|
2285
|
+
return { dispatchMode: "dom", pointerType, fallbackReason: "page-hidden" };
|
|
2286
|
+
}
|
|
2287
|
+
return { dispatchMode: "trusted-input", pointerType };
|
|
2288
|
+
}
|
|
2108
2289
|
function buildPageDragPath(from, to, durationMs) {
|
|
2109
2290
|
const steps = Math.max(PAGE_DRAG_MIN_STEPS, Math.min(PAGE_DRAG_MAX_STEPS, Math.ceil(durationMs / PAGE_DRAG_STEP_INTERVAL_MS)));
|
|
2110
2291
|
const points = Array.from({ length: steps }, (_, index) => {
|
|
@@ -2116,6 +2297,116 @@ function buildPageDragPath(from, to, durationMs) {
|
|
|
2116
2297
|
});
|
|
2117
2298
|
return { points, intervalMs: durationMs / steps };
|
|
2118
2299
|
}
|
|
2300
|
+
function dragBetweenPointsInPage(from, points, pointerType) {
|
|
2301
|
+
const target = document.elementFromPoint(from.x, from.y);
|
|
2302
|
+
if (!target) {
|
|
2303
|
+
return {
|
|
2304
|
+
dragged: false,
|
|
2305
|
+
steps: 0,
|
|
2306
|
+
error: `No element found at drag start point (${from.x}, ${from.y})`
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
if (points.length === 0) {
|
|
2310
|
+
return { dragged: false, steps: 0, error: "Drag path must contain at least one point" };
|
|
2311
|
+
}
|
|
2312
|
+
const view = target.ownerDocument.defaultView ?? window;
|
|
2313
|
+
const realm = view;
|
|
2314
|
+
const pointerId = 1;
|
|
2315
|
+
const pointerInit = (point, buttons, pressure) => ({
|
|
2316
|
+
bubbles: true,
|
|
2317
|
+
cancelable: true,
|
|
2318
|
+
composed: true,
|
|
2319
|
+
view,
|
|
2320
|
+
clientX: point.x,
|
|
2321
|
+
clientY: point.y,
|
|
2322
|
+
screenX: point.x,
|
|
2323
|
+
screenY: point.y,
|
|
2324
|
+
button: 0,
|
|
2325
|
+
buttons,
|
|
2326
|
+
pointerId,
|
|
2327
|
+
pointerType,
|
|
2328
|
+
isPrimary: true,
|
|
2329
|
+
pressure,
|
|
2330
|
+
width: 1,
|
|
2331
|
+
height: 1
|
|
2332
|
+
});
|
|
2333
|
+
const dispatchPointer = (type, point, buttons, pressure) => {
|
|
2334
|
+
if (typeof realm.PointerEvent !== "function")
|
|
2335
|
+
return false;
|
|
2336
|
+
target.dispatchEvent(new realm.PointerEvent(type, pointerInit(point, buttons, pressure)));
|
|
2337
|
+
return true;
|
|
2338
|
+
};
|
|
2339
|
+
const dispatchMouse = (type, point, buttons) => {
|
|
2340
|
+
if (typeof realm.MouseEvent !== "function")
|
|
2341
|
+
return false;
|
|
2342
|
+
target.dispatchEvent(new realm.MouseEvent(type, {
|
|
2343
|
+
bubbles: true,
|
|
2344
|
+
cancelable: true,
|
|
2345
|
+
composed: true,
|
|
2346
|
+
view,
|
|
2347
|
+
clientX: point.x,
|
|
2348
|
+
clientY: point.y,
|
|
2349
|
+
screenX: point.x,
|
|
2350
|
+
screenY: point.y,
|
|
2351
|
+
button: 0,
|
|
2352
|
+
buttons
|
|
2353
|
+
}));
|
|
2354
|
+
return true;
|
|
2355
|
+
};
|
|
2356
|
+
const createTouch = (point) => {
|
|
2357
|
+
const init = {
|
|
2358
|
+
identifier: pointerId,
|
|
2359
|
+
target,
|
|
2360
|
+
clientX: point.x,
|
|
2361
|
+
clientY: point.y,
|
|
2362
|
+
screenX: point.x,
|
|
2363
|
+
screenY: point.y,
|
|
2364
|
+
pageX: point.x + view.scrollX,
|
|
2365
|
+
pageY: point.y + view.scrollY,
|
|
2366
|
+
radiusX: 1,
|
|
2367
|
+
radiusY: 1,
|
|
2368
|
+
rotationAngle: 0,
|
|
2369
|
+
force: 0.5
|
|
2370
|
+
};
|
|
2371
|
+
return typeof realm.Touch === "function" ? new realm.Touch(init) : init;
|
|
2372
|
+
};
|
|
2373
|
+
const dispatchTouch = (type, point) => {
|
|
2374
|
+
if (typeof realm.TouchEvent !== "function")
|
|
2375
|
+
return false;
|
|
2376
|
+
const touch = createTouch(point);
|
|
2377
|
+
const activeTouches = type === "touchend" ? [] : [touch];
|
|
2378
|
+
target.dispatchEvent(new realm.TouchEvent(type, {
|
|
2379
|
+
bubbles: true,
|
|
2380
|
+
cancelable: true,
|
|
2381
|
+
composed: true,
|
|
2382
|
+
touches: activeTouches,
|
|
2383
|
+
targetTouches: activeTouches,
|
|
2384
|
+
changedTouches: [touch]
|
|
2385
|
+
}));
|
|
2386
|
+
return true;
|
|
2387
|
+
};
|
|
2388
|
+
let dispatched = dispatchPointer("pointerdown", from, 1, 0.5);
|
|
2389
|
+
dispatched = pointerType === "touch" ? dispatchTouch("touchstart", from) || dispatched : dispatchMouse("mousedown", from, 1) || dispatched;
|
|
2390
|
+
for (const point of points) {
|
|
2391
|
+
dispatched = dispatchPointer("pointermove", point, 1, 0.5) || dispatched;
|
|
2392
|
+
dispatched = pointerType === "touch" ? dispatchTouch("touchmove", point) || dispatched : dispatchMouse("mousemove", point, 1) || dispatched;
|
|
2393
|
+
}
|
|
2394
|
+
const end = points[points.length - 1];
|
|
2395
|
+
dispatched = pointerType === "touch" ? dispatchTouch("touchend", end) || dispatched : dispatchMouse("mouseup", end, 0) || dispatched;
|
|
2396
|
+
dispatched = dispatchPointer("pointerup", end, 0, 0) || dispatched;
|
|
2397
|
+
return dispatched ? { dragged: true, steps: points.length } : { dragged: false, steps: 0, error: "This page does not support synthetic drag events" };
|
|
2398
|
+
}
|
|
2399
|
+
var DRAG_BETWEEN_POINTS_IN_PAGE_SOURCE = dragBetweenPointsInPage.toString();
|
|
2400
|
+
function buildPageDragDomExpression(from, points, pointerType) {
|
|
2401
|
+
return `(${DRAG_BETWEEN_POINTS_IN_PAGE_SOURCE})(${JSON.stringify(from)}, ${JSON.stringify(points)}, ${JSON.stringify(pointerType)})`;
|
|
2402
|
+
}
|
|
2403
|
+
function unwrapPageDragDomResult(result) {
|
|
2404
|
+
if (!result)
|
|
2405
|
+
throw new Error("page_drag: DOM simulation returned no result");
|
|
2406
|
+
if (!result.dragged)
|
|
2407
|
+
throw new Error(`page_drag: ${result.error}`);
|
|
2408
|
+
return result;
|
|
2409
|
+
}
|
|
2119
2410
|
function getDragCoordsFromNode() {
|
|
2120
2411
|
const el = this.nodeType === Node.TEXT_NODE ? this.parentElement : this;
|
|
2121
2412
|
if (!el || typeof el.getBoundingClientRect !== "function")
|
|
@@ -3458,6 +3749,7 @@ var PAGE_ACT_ACTIONS = [
|
|
|
3458
3749
|
"page_eval"
|
|
3459
3750
|
];
|
|
3460
3751
|
var BROWSER_COMMAND_ACTIONS = {
|
|
3752
|
+
tab_checkpoint: true,
|
|
3461
3753
|
capture_checkpoint: true,
|
|
3462
3754
|
get_requests: true,
|
|
3463
3755
|
get_logs: true,
|
|
@@ -3502,6 +3794,7 @@ var BROWSER_COMMAND_ACTIONS = {
|
|
|
3502
3794
|
run_actions: true
|
|
3503
3795
|
};
|
|
3504
3796
|
var EXTENSION_BROWSER_ACTIONS = {
|
|
3797
|
+
tab_checkpoint: true,
|
|
3505
3798
|
capture_checkpoint: true,
|
|
3506
3799
|
get_requests: true,
|
|
3507
3800
|
get_logs: true,
|
|
@@ -3554,22 +3847,53 @@ var DEFAULT_PAGE_DIAGNOSTICS = {
|
|
|
3554
3847
|
logs: true,
|
|
3555
3848
|
requests: true
|
|
3556
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
|
+
}
|
|
3557
3880
|
function isPageActAction(action) {
|
|
3558
3881
|
return PAGE_ACT_ACTIONS.includes(action);
|
|
3559
3882
|
}
|
|
3560
3883
|
function shouldObservePageAction(action, data) {
|
|
3561
3884
|
if (!isPageActAction(action))
|
|
3562
3885
|
return false;
|
|
3563
|
-
const
|
|
3886
|
+
const diagnosticsSetting = data?.diagnostics;
|
|
3887
|
+
const diagnosticsEnabled = diagnosticsSetting === true || diagnosticsSetting !== null && typeof diagnosticsSetting === "object" && !Array.isArray(diagnosticsSetting);
|
|
3564
3888
|
if (action === "page_eval") {
|
|
3565
3889
|
return Boolean(diagnosticsEnabled || data?.observe && typeof data.observe === "object" && !Array.isArray(data.observe));
|
|
3566
3890
|
}
|
|
3567
|
-
return data?.observe !== false ||
|
|
3891
|
+
return data?.observe !== false || diagnosticsEnabled;
|
|
3568
3892
|
}
|
|
3569
3893
|
function splitObservedActionPayload(action, data) {
|
|
3570
3894
|
const { observe, diagnostics, ...actionData } = data ?? {};
|
|
3571
3895
|
const setting = diagnostics;
|
|
3572
|
-
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;
|
|
3573
3897
|
if (observe === false)
|
|
3574
3898
|
return { actionData, observe: false, diagnostics: resolvedDiagnostics };
|
|
3575
3899
|
if (action === "page_eval" && (!observe || typeof observe !== "object")) {
|
|
@@ -3632,11 +3956,25 @@ async function runPageAct(input, dependencies) {
|
|
|
3632
3956
|
sourceTabId
|
|
3633
3957
|
}).catch(() => null);
|
|
3634
3958
|
let actionResult;
|
|
3635
|
-
let
|
|
3959
|
+
let actionFailure;
|
|
3636
3960
|
try {
|
|
3637
3961
|
actionResult = await dependencies.executeAction(typedAction, actionData);
|
|
3638
3962
|
} catch (error48) {
|
|
3639
|
-
|
|
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
|
+
};
|
|
3640
3978
|
}
|
|
3641
3979
|
let observation;
|
|
3642
3980
|
try {
|
|
@@ -3645,13 +3983,13 @@ async function runPageAct(input, dependencies) {
|
|
|
3645
3983
|
observation = unavailableObservation(checkpoint, error48);
|
|
3646
3984
|
}
|
|
3647
3985
|
try {
|
|
3648
|
-
if (navigationObserver &&
|
|
3986
|
+
if (navigationObserver && actionFailure === void 0 && (navigationObserver.hasStarted() || shouldWaitForPotentialNavigation(typedAction, observation))) {
|
|
3649
3987
|
const didNavigate = navigationObserver.hasStarted() || await navigationObserver.waitForStart(navigationOptions.graceMs);
|
|
3650
3988
|
if (didNavigate) {
|
|
3651
3989
|
observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
|
|
3652
3990
|
}
|
|
3653
3991
|
}
|
|
3654
|
-
if (navigationObserver &&
|
|
3992
|
+
if (navigationObserver && actionFailure === void 0 && observation.mode !== "navigation" && navigationObserver.hasNetworkActivity()) {
|
|
3655
3993
|
const network = await navigationObserver.waitForNetworkIdle(navigationOptions.timeoutMs);
|
|
3656
3994
|
if (navigationObserver.hasStarted()) {
|
|
3657
3995
|
observation = await navigationObserver.waitForResult(navigationOptions.timeoutMs);
|
|
@@ -3686,9 +4024,9 @@ async function runPageAct(input, dependencies) {
|
|
|
3686
4024
|
return {
|
|
3687
4025
|
action: {
|
|
3688
4026
|
name: typedAction,
|
|
3689
|
-
success:
|
|
4027
|
+
success: actionFailure === void 0,
|
|
3690
4028
|
...actionResult !== void 0 ? { data: actionResult } : {},
|
|
3691
|
-
...
|
|
4029
|
+
...actionFailure ? actionFailure : {}
|
|
3692
4030
|
},
|
|
3693
4031
|
observation: compactObservationResponse(observation, checkpoint)
|
|
3694
4032
|
};
|
|
@@ -4446,83 +4784,6 @@ ${SOURCE_BADGES[source]}
|
|
|
4446
4784
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
4447
4785
|
}
|
|
4448
4786
|
|
|
4449
|
-
// ../browser-core/dist/capture.js
|
|
4450
|
-
var CURSOR_PREFIX = "mearl-capture-v1";
|
|
4451
|
-
function createCaptureGeneration() {
|
|
4452
|
-
return crypto.randomUUID();
|
|
4453
|
-
}
|
|
4454
|
-
function encodeCaptureCursor(cursor) {
|
|
4455
|
-
return `${CURSOR_PREFIX}:${cursor.tabId}:${cursor.generation}:${cursor.scope}:${cursor.sequence}`;
|
|
4456
|
-
}
|
|
4457
|
-
function decodeCaptureCursor(value) {
|
|
4458
|
-
const match = value.match(/^mearl-capture-v1:(\d+):([^:]+):(all|logs|requests):(\d+)$/);
|
|
4459
|
-
if (!match)
|
|
4460
|
-
throw new Error("Invalid capture cursor");
|
|
4461
|
-
const tabId = Number(match[1]);
|
|
4462
|
-
const sequence = Number(match[4]);
|
|
4463
|
-
if (!Number.isSafeInteger(tabId) || !Number.isSafeInteger(sequence)) {
|
|
4464
|
-
throw new Error("Invalid capture cursor");
|
|
4465
|
-
}
|
|
4466
|
-
return {
|
|
4467
|
-
tabId,
|
|
4468
|
-
generation: match[2],
|
|
4469
|
-
scope: match[3],
|
|
4470
|
-
sequence
|
|
4471
|
-
};
|
|
4472
|
-
}
|
|
4473
|
-
function captureCheckpoint(state) {
|
|
4474
|
-
return encodeCaptureCursor({
|
|
4475
|
-
tabId: state.tabId,
|
|
4476
|
-
generation: state.generation,
|
|
4477
|
-
sequence: state.currentSequence,
|
|
4478
|
-
scope: "all"
|
|
4479
|
-
});
|
|
4480
|
-
}
|
|
4481
|
-
function nextCaptureSequence(state) {
|
|
4482
|
-
state.currentSequence += 1;
|
|
4483
|
-
return state.currentSequence;
|
|
4484
|
-
}
|
|
4485
|
-
function resolveAfterSequence(after, state, scope) {
|
|
4486
|
-
if (!after)
|
|
4487
|
-
return 0;
|
|
4488
|
-
const cursor = decodeCaptureCursor(after);
|
|
4489
|
-
if (cursor.tabId !== state.tabId) {
|
|
4490
|
-
throw new Error(`Capture cursor belongs to tab ${cursor.tabId}, not tab ${state.tabId}`);
|
|
4491
|
-
}
|
|
4492
|
-
if (cursor.generation !== state.generation) {
|
|
4493
|
-
throw new Error("Capture cursor expired because the tab collector was restarted");
|
|
4494
|
-
}
|
|
4495
|
-
if (cursor.scope !== "all" && cursor.scope !== scope) {
|
|
4496
|
-
throw new Error(`Capture cursor is scoped to ${cursor.scope}, not ${scope}`);
|
|
4497
|
-
}
|
|
4498
|
-
if (cursor.sequence > state.currentSequence) {
|
|
4499
|
-
throw new Error("Capture cursor is ahead of the current collector state");
|
|
4500
|
-
}
|
|
4501
|
-
return cursor.sequence;
|
|
4502
|
-
}
|
|
4503
|
-
function queryCapturedEntries(buffer, state, options, matches, droppedThroughSequence = 0, scope = "all") {
|
|
4504
|
-
const { after, since, now = Date.now() } = options;
|
|
4505
|
-
const limit = Math.max(1, Math.floor(options.limit ?? 100));
|
|
4506
|
-
const afterSequence = resolveAfterSequence(after, state, scope);
|
|
4507
|
-
const sinceTimestampMs = since === void 0 ? void 0 : now - since * 1e3;
|
|
4508
|
-
const matched = buffer.filter((entry) => entry.captureSequence > afterSequence && (sinceTimestampMs === void 0 || entry.timestampMs >= sinceTimestampMs) && matches(entry));
|
|
4509
|
-
const hasMore = matched.length > limit;
|
|
4510
|
-
const entries = after ? matched.slice(0, limit) : matched.slice(-limit);
|
|
4511
|
-
const nextSequence = after && hasMore && entries.length > 0 ? entries[entries.length - 1].captureSequence : state.currentSequence;
|
|
4512
|
-
return {
|
|
4513
|
-
entries,
|
|
4514
|
-
totalMatched: matched.length,
|
|
4515
|
-
nextCursor: encodeCaptureCursor({
|
|
4516
|
-
tabId: state.tabId,
|
|
4517
|
-
generation: state.generation,
|
|
4518
|
-
sequence: nextSequence,
|
|
4519
|
-
scope
|
|
4520
|
-
}),
|
|
4521
|
-
hasMore,
|
|
4522
|
-
gap: after !== void 0 && afterSequence < droppedThroughSequence
|
|
4523
|
-
};
|
|
4524
|
-
}
|
|
4525
|
-
|
|
4526
4787
|
// src/cdp-backend/domain-manager.ts
|
|
4527
4788
|
var enabledDomains = /* @__PURE__ */ new WeakMap();
|
|
4528
4789
|
function ensureCdpDomain(client2, domain2, params = {}) {
|
|
@@ -4558,7 +4819,8 @@ function createBuffers(tabId = 0) {
|
|
|
4558
4819
|
generation: createCaptureGeneration(),
|
|
4559
4820
|
currentSequence: 0,
|
|
4560
4821
|
droppedRequestsThroughSequence: 0,
|
|
4561
|
-
droppedLogsThroughSequence: 0
|
|
4822
|
+
droppedLogsThroughSequence: 0,
|
|
4823
|
+
droppedEventsThroughSequence: 0
|
|
4562
4824
|
}
|
|
4563
4825
|
};
|
|
4564
4826
|
}
|
|
@@ -4621,15 +4883,20 @@ function parsePostData(postData) {
|
|
|
4621
4883
|
}
|
|
4622
4884
|
}
|
|
4623
4885
|
}
|
|
4624
|
-
function appendTrackingEvents(buffers, url2, postData,
|
|
4886
|
+
function appendTrackingEvents(buffers, url2, postData, batchSequence, captureSequence) {
|
|
4625
4887
|
const events = parseTrackingRequest(url2, postData, {
|
|
4626
|
-
sequence,
|
|
4888
|
+
sequence: batchSequence,
|
|
4627
4889
|
now: Date.now()
|
|
4628
|
-
});
|
|
4890
|
+
}).map((event) => ({ ...event, captureSequence }));
|
|
4629
4891
|
if (events.length === 0) return false;
|
|
4630
|
-
buffers.events
|
|
4631
|
-
|
|
4632
|
-
|
|
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
|
+
}
|
|
4633
4900
|
}
|
|
4634
4901
|
return true;
|
|
4635
4902
|
}
|
|
@@ -4667,10 +4934,19 @@ async function setupBufferListeners(client2, buffers) {
|
|
|
4667
4934
|
const url2 = params.request?.url ?? "";
|
|
4668
4935
|
if (!url2.startsWith("http")) return;
|
|
4669
4936
|
if (isTrackingUrl(url2)) {
|
|
4670
|
-
const
|
|
4671
|
-
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
|
+
);
|
|
4672
4946
|
if (!captured && params.request?.hasPostData && !params.request?.postData) {
|
|
4673
|
-
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(() => {
|
|
4674
4950
|
});
|
|
4675
4951
|
}
|
|
4676
4952
|
return;
|
|
@@ -6470,17 +6746,27 @@ async function collectCdpPageDiagnostics(session, after, diagnostics) {
|
|
|
6470
6746
|
};
|
|
6471
6747
|
result.requests = await cdpGetRequests(session, { ...options, after });
|
|
6472
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
|
+
}
|
|
6473
6753
|
return result;
|
|
6474
6754
|
}
|
|
6475
6755
|
async function cdpGetEvents(session, data) {
|
|
6476
|
-
const { source = "all", event_type, since, limit = 20, filter, includeRaw = false } = data;
|
|
6477
|
-
const response =
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
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
|
+
);
|
|
6484
6770
|
const captureStatus = response.totalInBuffer === 0 ? "armed" : "active";
|
|
6485
6771
|
const hint = response.events.length === 0 && captureStatus === "armed" ? TRACKING_CAPTURE_ARMED_HINT : void 0;
|
|
6486
6772
|
return {
|
|
@@ -6492,12 +6778,17 @@ async function cdpGetEvents(session, data) {
|
|
|
6492
6778
|
captureStatus,
|
|
6493
6779
|
captureSource: "cdp",
|
|
6494
6780
|
includeRaw,
|
|
6781
|
+
after,
|
|
6782
|
+
nextCursor: response.nextCursor,
|
|
6783
|
+
hasMore: response.hasMore,
|
|
6784
|
+
gap: response.gap,
|
|
6495
6785
|
hint
|
|
6496
6786
|
},
|
|
6497
6787
|
events: response.events.map((event) => formatTrackingEvent(event, includeRaw))
|
|
6498
6788
|
};
|
|
6499
6789
|
}
|
|
6500
6790
|
registerActions([
|
|
6791
|
+
{ name: "tab_checkpoint", session: "page", handler: cdpCaptureCheckpoint },
|
|
6501
6792
|
{ name: "capture_checkpoint", session: "page", handler: cdpCaptureCheckpoint },
|
|
6502
6793
|
{ name: "get_requests", session: "page", handler: cdpGetRequests },
|
|
6503
6794
|
{ name: "get_logs", session: "page", handler: cdpGetLogs },
|
|
@@ -6588,19 +6879,23 @@ async function cdpObservedPageAction(session, action, data, controlSource = "loc
|
|
|
6588
6879
|
if (!diagnostics) return dispatchCdpAction(session, action, actionData, controlSource);
|
|
6589
6880
|
const cursor = captureCheckpoint(pageSession.buffers.capture);
|
|
6590
6881
|
let actionResult;
|
|
6591
|
-
let
|
|
6882
|
+
let actionFailure;
|
|
6592
6883
|
try {
|
|
6593
6884
|
actionResult = await dispatchCdpAction(session, action, actionData, controlSource);
|
|
6594
6885
|
} catch (error48) {
|
|
6595
|
-
|
|
6886
|
+
actionFailure = describePageActionFailure(error48);
|
|
6596
6887
|
}
|
|
6597
|
-
|
|
6888
|
+
const result2 = {
|
|
6598
6889
|
action: {
|
|
6599
6890
|
name: action,
|
|
6600
|
-
success:
|
|
6891
|
+
success: actionFailure === void 0,
|
|
6601
6892
|
...actionResult !== void 0 ? { data: actionResult } : {},
|
|
6602
|
-
...
|
|
6603
|
-
}
|
|
6893
|
+
...actionFailure ? actionFailure : {}
|
|
6894
|
+
}
|
|
6895
|
+
};
|
|
6896
|
+
if (actionFailure?.stage === "precondition") return result2;
|
|
6897
|
+
return {
|
|
6898
|
+
...result2,
|
|
6604
6899
|
diagnostics: await collectCdpPageDiagnostics(pageSession, cursor, diagnostics)
|
|
6605
6900
|
};
|
|
6606
6901
|
}
|
|
@@ -6624,7 +6919,7 @@ async function cdpObservedPageAction(session, action, data, controlSource = "loc
|
|
|
6624
6919
|
disposeObservation: () => disposePageObservation(pageSession)
|
|
6625
6920
|
}
|
|
6626
6921
|
);
|
|
6627
|
-
if (!diagnostics || !diagnosticsCursor) return result;
|
|
6922
|
+
if (!diagnostics || !diagnosticsCursor || result.action.stage === "precondition") return result;
|
|
6628
6923
|
return {
|
|
6629
6924
|
...result,
|
|
6630
6925
|
diagnostics: await collectCdpPageDiagnostics(pageSession, diagnosticsCursor, diagnostics)
|
|
@@ -8109,7 +8404,9 @@ var GET_DRAG_COORDS_FROM_NODE_SRC = getDragCoordsFromNode.toString();
|
|
|
8109
8404
|
function resolveSnapshotRef(session, ref) {
|
|
8110
8405
|
const entry = resolvePageRef(session, ref);
|
|
8111
8406
|
if (!entry) {
|
|
8112
|
-
throw new
|
|
8407
|
+
throw new PageActionPreconditionError(
|
|
8408
|
+
`Ref ${ref} not found. Run page_snapshot first to get fresh refs.`
|
|
8409
|
+
);
|
|
8113
8410
|
}
|
|
8114
8411
|
return entry;
|
|
8115
8412
|
}
|
|
@@ -8144,7 +8441,7 @@ async function clickBySelectorJs(session, selector) {
|
|
|
8144
8441
|
});
|
|
8145
8442
|
const value = result.result?.value;
|
|
8146
8443
|
if (!value) throw new Error("click evaluation returned no value");
|
|
8147
|
-
if (value.error) throw new
|
|
8444
|
+
if (value.error) throw new PageActionPreconditionError(value.error);
|
|
8148
8445
|
return { ...value, dispatchMode: "dom" };
|
|
8149
8446
|
}
|
|
8150
8447
|
async function clickByBackendNodeJs(session, backendNodeId) {
|
|
@@ -8155,6 +8452,9 @@ async function clickByBackendNodeJs(session, backendNodeId) {
|
|
|
8155
8452
|
returnByValue: true
|
|
8156
8453
|
});
|
|
8157
8454
|
const value = result.result?.value;
|
|
8455
|
+
if (value === null) {
|
|
8456
|
+
throw new PageActionPreconditionError("Cannot resolve clickable element");
|
|
8457
|
+
}
|
|
8158
8458
|
if (!value) throw new Error("click evaluation returned no value");
|
|
8159
8459
|
return {
|
|
8160
8460
|
tag: value.tag,
|
|
@@ -8175,11 +8475,12 @@ async function clickByPointJs(session, point) {
|
|
|
8175
8475
|
returnByValue: true
|
|
8176
8476
|
});
|
|
8177
8477
|
const value = result.result?.value;
|
|
8178
|
-
if (
|
|
8179
|
-
throw new
|
|
8478
|
+
if (value === null) {
|
|
8479
|
+
throw new PageActionPreconditionError(
|
|
8180
8480
|
`page_click: no element found at point (${point.x}, ${point.y}) in the current viewport.`
|
|
8181
8481
|
);
|
|
8182
8482
|
}
|
|
8483
|
+
if (!value) throw new Error("click evaluation returned no value");
|
|
8183
8484
|
return {
|
|
8184
8485
|
tag: value.tag,
|
|
8185
8486
|
text: value.text,
|
|
@@ -8217,7 +8518,7 @@ async function getElementCoordsBySelector(session, selector) {
|
|
|
8217
8518
|
});
|
|
8218
8519
|
const value = result.result?.value;
|
|
8219
8520
|
if (!value) throw new Error("coord evaluation returned no value");
|
|
8220
|
-
if (value.error) throw new
|
|
8521
|
+
if (value.error) throw new PageActionPreconditionError(value.error);
|
|
8221
8522
|
return value;
|
|
8222
8523
|
}
|
|
8223
8524
|
async function getElementCoordsByBackendNodeId(session, backendNodeId) {
|
|
@@ -8228,13 +8529,16 @@ async function getElementCoordsByBackendNodeId(session, backendNodeId) {
|
|
|
8228
8529
|
returnByValue: true
|
|
8229
8530
|
});
|
|
8230
8531
|
const value = result.result?.value;
|
|
8231
|
-
if (
|
|
8232
|
-
throw new
|
|
8532
|
+
if (value === null) {
|
|
8533
|
+
throw new PageActionPreconditionError(
|
|
8534
|
+
`Cannot resolve clickable element for backendNodeId ${backendNodeId}`
|
|
8535
|
+
);
|
|
8233
8536
|
}
|
|
8537
|
+
if (!value) throw new Error("coord evaluation returned no value");
|
|
8234
8538
|
return value;
|
|
8235
8539
|
}
|
|
8236
8540
|
async function resolveScopeBackendNodeId(session, scope) {
|
|
8237
|
-
validatePageElementSelector(scope, "page_click scope");
|
|
8541
|
+
checkPageActionPrecondition(() => validatePageElementSelector(scope, "page_click scope"));
|
|
8238
8542
|
if (scope.startsWith("@e")) {
|
|
8239
8543
|
return resolveSnapshotRef(session, scope).backendNodeId;
|
|
8240
8544
|
}
|
|
@@ -8244,17 +8548,17 @@ async function resolveScopeBackendNodeId(session, scope) {
|
|
|
8244
8548
|
});
|
|
8245
8549
|
if (evaluated.exceptionDetails) {
|
|
8246
8550
|
const message = evaluated.exceptionDetails.exception?.description ?? evaluated.exceptionDetails.text ?? "invalid selector";
|
|
8247
|
-
throw new
|
|
8551
|
+
throw new PageActionPreconditionError(`page_click: invalid scope "${scope}": ${message}`);
|
|
8248
8552
|
}
|
|
8249
8553
|
const objectId = evaluated.result?.objectId;
|
|
8250
8554
|
if (!objectId || evaluated.result?.subtype === "null") {
|
|
8251
|
-
throw new
|
|
8555
|
+
throw new PageActionPreconditionError(`page_click: scope not found: ${scope}`);
|
|
8252
8556
|
}
|
|
8253
8557
|
try {
|
|
8254
8558
|
const described = await session.client.sendCommand("DOM.describeNode", { objectId });
|
|
8255
8559
|
const backendNodeId = described.node?.backendNodeId;
|
|
8256
8560
|
if (backendNodeId === void 0) {
|
|
8257
|
-
throw new
|
|
8561
|
+
throw new PageActionPreconditionError(`page_click: cannot resolve scope: ${scope}`);
|
|
8258
8562
|
}
|
|
8259
8563
|
return backendNodeId;
|
|
8260
8564
|
} finally {
|
|
@@ -8271,21 +8575,28 @@ async function cdpPageClick(session, data) {
|
|
|
8271
8575
|
Boolean
|
|
8272
8576
|
);
|
|
8273
8577
|
if (clickMode !== void 0 && !["auto", "dom", "mouse", "touch"].includes(clickMode)) {
|
|
8274
|
-
throw new
|
|
8578
|
+
throw new PageActionPreconditionError(
|
|
8579
|
+
'page_click: "clickMode" must be one of auto, dom, mouse, touch'
|
|
8580
|
+
);
|
|
8275
8581
|
}
|
|
8276
8582
|
if (scope !== void 0 && !hasText) {
|
|
8277
|
-
throw new
|
|
8583
|
+
throw new PageActionPreconditionError(
|
|
8584
|
+
'page_click: "scope" is only supported with the "text" target'
|
|
8585
|
+
);
|
|
8278
8586
|
}
|
|
8279
8587
|
if (scope !== void 0) {
|
|
8280
|
-
validatePageElementSelector(scope, "page_click scope");
|
|
8588
|
+
checkPageActionPrecondition(() => validatePageElementSelector(scope, "page_click scope"));
|
|
8589
|
+
}
|
|
8590
|
+
if (hasSelector) {
|
|
8591
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_click"));
|
|
8281
8592
|
}
|
|
8282
8593
|
if (provided.length === 0) {
|
|
8283
|
-
throw new
|
|
8594
|
+
throw new PageActionPreconditionError(
|
|
8284
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.'
|
|
8285
8596
|
);
|
|
8286
8597
|
}
|
|
8287
8598
|
if (provided.length > 1) {
|
|
8288
|
-
throw new
|
|
8599
|
+
throw new PageActionPreconditionError(
|
|
8289
8600
|
`page_click: provide exactly one of "selector", "text", or "point" — got ${provided.length} (${provided.join(", ")}).`
|
|
8290
8601
|
);
|
|
8291
8602
|
}
|
|
@@ -8363,13 +8674,13 @@ async function cdpPageClick(session, data) {
|
|
|
8363
8674
|
if (hits.length === 0) {
|
|
8364
8675
|
const exactSuffix = exact === false ? " (substring match)" : "";
|
|
8365
8676
|
const roleSuffix = role ? ` with role "${role}"` : "";
|
|
8366
|
-
throw new
|
|
8677
|
+
throw new PageActionPreconditionError(
|
|
8367
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.`
|
|
8368
8679
|
);
|
|
8369
8680
|
}
|
|
8370
8681
|
if (hits.length > 1) {
|
|
8371
8682
|
const candidates = formatAXTextMatchCandidates(hits);
|
|
8372
|
-
throw new
|
|
8683
|
+
throw new PageActionPreconditionError(
|
|
8373
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:
|
|
8374
8685
|
${candidates}`
|
|
8375
8686
|
);
|
|
@@ -8399,7 +8710,7 @@ ${candidates}`
|
|
|
8399
8710
|
)
|
|
8400
8711
|
);
|
|
8401
8712
|
}
|
|
8402
|
-
if (!point) throw new
|
|
8713
|
+
if (!point) throw new PageActionPreconditionError("page_click point is required");
|
|
8403
8714
|
if (effectiveClickMode === "dom") {
|
|
8404
8715
|
const dispatchTarget = await clickByPointJs(session, point);
|
|
8405
8716
|
return finalize2(createPageClickResult({ kind: "point", point }, dispatchTarget));
|
|
@@ -8488,19 +8799,53 @@ async function resolveDragEndpoint(session, endpoint) {
|
|
|
8488
8799
|
};
|
|
8489
8800
|
}
|
|
8490
8801
|
async function cdpPageDrag(session, data) {
|
|
8491
|
-
const input = resolvePageDragInput(data);
|
|
8802
|
+
const input = checkPageActionPrecondition(() => resolvePageDragInput(data));
|
|
8803
|
+
const visibilityResult = input.dragMode === "auto" ? await session.client.sendCommand("Runtime.evaluate", {
|
|
8804
|
+
expression: "document.visibilityState",
|
|
8805
|
+
returnByValue: true
|
|
8806
|
+
}) : void 0;
|
|
8807
|
+
const touchEmulationEnabled = input.dragMode === "auto" || input.dragMode === "dom" ? await prefersTouchInput(session) : input.dragMode === "touch";
|
|
8808
|
+
const policy = resolvePageDragDispatchPolicy(
|
|
8809
|
+
input.dragMode,
|
|
8810
|
+
visibilityResult?.result?.value,
|
|
8811
|
+
touchEmulationEnabled
|
|
8812
|
+
);
|
|
8492
8813
|
await scrollDragSourceIntoView(session, input.from);
|
|
8493
8814
|
const from = await resolveDragEndpoint(session, input.from);
|
|
8494
8815
|
const to = await resolveDragEndpoint(session, input.to);
|
|
8495
8816
|
if (Math.hypot(to.point.x - from.point.x, to.point.y - from.point.y) < 1) {
|
|
8496
8817
|
throw new Error("page_drag: resolved endpoints must be at least 1 viewport pixel apart");
|
|
8497
8818
|
}
|
|
8498
|
-
|
|
8819
|
+
if (policy.dispatchMode === "dom") {
|
|
8820
|
+
const path15 = buildPageDragPath(from.point, to.point, input.durationMs);
|
|
8821
|
+
const result = await session.client.sendCommand("Runtime.evaluate", {
|
|
8822
|
+
expression: buildPageDragDomExpression(from.point, path15.points, policy.pointerType),
|
|
8823
|
+
returnByValue: true
|
|
8824
|
+
});
|
|
8825
|
+
if (result.exceptionDetails) {
|
|
8826
|
+
throw new Error(
|
|
8827
|
+
result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? "page_drag: DOM simulation failed"
|
|
8828
|
+
);
|
|
8829
|
+
}
|
|
8830
|
+
const { steps: steps2 } = unwrapPageDragDomResult(
|
|
8831
|
+
result.result?.value
|
|
8832
|
+
);
|
|
8833
|
+
return {
|
|
8834
|
+
dragged: true,
|
|
8835
|
+
from,
|
|
8836
|
+
to,
|
|
8837
|
+
dispatchMode: "dom",
|
|
8838
|
+
pointerType: policy.pointerType,
|
|
8839
|
+
durationMs: input.durationMs,
|
|
8840
|
+
steps: steps2,
|
|
8841
|
+
...policy.fallbackReason ? { fallbackReason: policy.fallbackReason } : {}
|
|
8842
|
+
};
|
|
8843
|
+
}
|
|
8499
8844
|
const { steps } = await dispatchCdpDrag(
|
|
8500
8845
|
session,
|
|
8501
8846
|
from.point,
|
|
8502
8847
|
to.point,
|
|
8503
|
-
pointerType,
|
|
8848
|
+
policy.pointerType,
|
|
8504
8849
|
input.durationMs
|
|
8505
8850
|
);
|
|
8506
8851
|
return {
|
|
@@ -8508,7 +8853,7 @@ async function cdpPageDrag(session, data) {
|
|
|
8508
8853
|
from,
|
|
8509
8854
|
to,
|
|
8510
8855
|
dispatchMode: "trusted-input",
|
|
8511
|
-
pointerType,
|
|
8856
|
+
pointerType: policy.pointerType,
|
|
8512
8857
|
durationMs: input.durationMs,
|
|
8513
8858
|
steps
|
|
8514
8859
|
};
|
|
@@ -8516,11 +8861,14 @@ async function cdpPageDrag(session, data) {
|
|
|
8516
8861
|
async function cdpPageType(session, data) {
|
|
8517
8862
|
const { selector, text, clearFirst = true } = data;
|
|
8518
8863
|
if (!selector || typeof selector !== "string") {
|
|
8519
|
-
throw new
|
|
8864
|
+
throw new PageActionPreconditionError(
|
|
8520
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".`
|
|
8521
8866
|
);
|
|
8522
8867
|
}
|
|
8523
|
-
|
|
8868
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_type"));
|
|
8869
|
+
if (typeof text !== "string") {
|
|
8870
|
+
throw new PageActionPreconditionError('"text" must be a string.');
|
|
8871
|
+
}
|
|
8524
8872
|
if (selector.startsWith("@e")) {
|
|
8525
8873
|
return withSnapshotRef(session, selector, async (entry) => {
|
|
8526
8874
|
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
@@ -8534,7 +8882,7 @@ async function cdpPageType(session, data) {
|
|
|
8534
8882
|
});
|
|
8535
8883
|
const value2 = result.result?.value;
|
|
8536
8884
|
if (!value2) throw new Error("cdp_page_type: evaluation returned no value");
|
|
8537
|
-
if (value2.error) throw new
|
|
8885
|
+
if (value2.error) throw new PageActionPreconditionError(value2.error);
|
|
8538
8886
|
return { typed: true, tag: value2.tag };
|
|
8539
8887
|
});
|
|
8540
8888
|
}
|
|
@@ -8552,12 +8900,12 @@ async function cdpPageType(session, data) {
|
|
|
8552
8900
|
});
|
|
8553
8901
|
const value = r.result?.value;
|
|
8554
8902
|
if (!value) throw new Error("cdp_page_type: evaluation returned no value");
|
|
8555
|
-
if (value.error) throw new
|
|
8556
|
-
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");
|
|
8557
8905
|
return { typed: true, tag: value.tag };
|
|
8558
8906
|
}
|
|
8559
8907
|
async function cdpPageScroll(session, data) {
|
|
8560
|
-
const input = resolvePageScrollInput(data ?? {});
|
|
8908
|
+
const input = checkPageActionPrecondition(() => resolvePageScrollInput(data ?? {}));
|
|
8561
8909
|
if (input.selector?.startsWith("@e")) {
|
|
8562
8910
|
return withSnapshotRef(session, input.selector, async (entry) => {
|
|
8563
8911
|
const objectId = await resolveBackendNodeObjectId(session, entry.backendNodeId);
|
|
@@ -8595,7 +8943,7 @@ async function cdpPageScroll(session, data) {
|
|
|
8595
8943
|
async function cdpPageEval(session, data) {
|
|
8596
8944
|
const { expression, awaitPromise = true } = data;
|
|
8597
8945
|
if (!expression || typeof expression !== "string") {
|
|
8598
|
-
throw new
|
|
8946
|
+
throw new PageActionPreconditionError("Invalid expression");
|
|
8599
8947
|
}
|
|
8600
8948
|
const result = await session.client.sendCommand("Runtime.evaluate", {
|
|
8601
8949
|
expression,
|
|
@@ -8611,7 +8959,7 @@ async function cdpPageEval(session, data) {
|
|
|
8611
8959
|
}
|
|
8612
8960
|
async function cdpPagePress(session, data) {
|
|
8613
8961
|
const { key } = data;
|
|
8614
|
-
if (!key || typeof key !== "string") throw new
|
|
8962
|
+
if (!key || typeof key !== "string") throw new PageActionPreconditionError("Invalid key");
|
|
8615
8963
|
await session.client.sendCommand("Input.dispatchKeyEvent", {
|
|
8616
8964
|
type: "keyDown",
|
|
8617
8965
|
key
|
|
@@ -8729,10 +9077,15 @@ async function waitForHistoryIndex(session, targetIndex, timeoutMs) {
|
|
|
8729
9077
|
async function cdpPageUpload(session, data) {
|
|
8730
9078
|
const { selector, filePaths } = data ?? {};
|
|
8731
9079
|
if (typeof selector !== "string" || selector.length === 0) {
|
|
8732
|
-
throw new
|
|
9080
|
+
throw new PageActionPreconditionError(
|
|
9081
|
+
'page_upload: "selector" is required (CSS selector or @eN snapshot ref)'
|
|
9082
|
+
);
|
|
8733
9083
|
}
|
|
9084
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_upload"));
|
|
8734
9085
|
if (!Array.isArray(filePaths) || filePaths.length === 0 || filePaths.some((filePath) => typeof filePath !== "string" || filePath.length === 0)) {
|
|
8735
|
-
throw new
|
|
9086
|
+
throw new PageActionPreconditionError(
|
|
9087
|
+
"Invalid filePaths: must be a non-empty array of file path strings"
|
|
9088
|
+
);
|
|
8736
9089
|
}
|
|
8737
9090
|
let described;
|
|
8738
9091
|
if (selector.startsWith("@e")) {
|
|
@@ -8749,16 +9102,20 @@ async function cdpPageUpload(session, data) {
|
|
|
8749
9102
|
nodeId: documentResult.root?.nodeId,
|
|
8750
9103
|
selector
|
|
8751
9104
|
});
|
|
8752
|
-
if (!queryResult.nodeId)
|
|
9105
|
+
if (!queryResult.nodeId) {
|
|
9106
|
+
throw new PageActionPreconditionError(`Element not found: ${selector}`);
|
|
9107
|
+
}
|
|
8753
9108
|
described = await session.client.sendCommand("DOM.describeNode", {
|
|
8754
9109
|
nodeId: queryResult.nodeId
|
|
8755
9110
|
});
|
|
8756
9111
|
}
|
|
8757
9112
|
const backendNodeId = described.node?.backendNodeId;
|
|
8758
9113
|
const tag = String(described.node?.nodeName ?? "").toLowerCase();
|
|
8759
|
-
if (backendNodeId === void 0)
|
|
9114
|
+
if (backendNodeId === void 0) {
|
|
9115
|
+
throw new PageActionPreconditionError(`Cannot resolve file input: ${selector}`);
|
|
9116
|
+
}
|
|
8760
9117
|
if (tag !== "input") {
|
|
8761
|
-
throw new
|
|
9118
|
+
throw new PageActionPreconditionError(
|
|
8762
9119
|
`Element <${tag || "unknown"}> is not a file input. selector must point to an <input type="file"> element.`
|
|
8763
9120
|
);
|
|
8764
9121
|
}
|
|
@@ -8817,8 +9174,11 @@ async function cdpSetDeviceEmulation(session, data) {
|
|
|
8817
9174
|
async function cdpPageHover(session, data) {
|
|
8818
9175
|
const { selector } = data ?? {};
|
|
8819
9176
|
if (typeof selector !== "string" || selector.length === 0) {
|
|
8820
|
-
throw new
|
|
9177
|
+
throw new PageActionPreconditionError(
|
|
9178
|
+
'page_hover: "selector" is required (CSS selector or @eN snapshot ref)'
|
|
9179
|
+
);
|
|
8821
9180
|
}
|
|
9181
|
+
checkPageActionPrecondition(() => validatePageElementSelector(selector, "page_hover"));
|
|
8822
9182
|
const coord = selector.startsWith("@e") ? await withSnapshotRef(
|
|
8823
9183
|
session,
|
|
8824
9184
|
selector,
|
|
@@ -10085,6 +10445,11 @@ async function requireSessionFromClient(agentBay, sessionId) {
|
|
|
10085
10445
|
}
|
|
10086
10446
|
return result.session;
|
|
10087
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
|
+
}
|
|
10088
10453
|
async function getAgentBayAccessUrl(sessionId, dependencies = {}) {
|
|
10089
10454
|
const apiKey = (dependencies.resolveApiKey ?? resolveAgentBayApiKey)();
|
|
10090
10455
|
const agentBay = dependencies.createAgentBayClient ? await dependencies.createAgentBayClient(apiKey) : await createClient(apiKey);
|
|
@@ -10438,6 +10803,83 @@ async function closeAgentBayBrowser(selector, options = {}) {
|
|
|
10438
10803
|
}
|
|
10439
10804
|
}
|
|
10440
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
|
+
|
|
10441
10883
|
// src/browser-registry.ts
|
|
10442
10884
|
var BROWSERS_SUBDIR = "browsers";
|
|
10443
10885
|
var STALE_RECORD_TTL_MS = 5 * 6e4;
|
|
@@ -10631,10 +11073,10 @@ function listBrowserInventory() {
|
|
|
10631
11073
|
import fs3 from "node:fs";
|
|
10632
11074
|
import path3 from "node:path";
|
|
10633
11075
|
import os2 from "node:os";
|
|
10634
|
-
import { randomUUID as
|
|
11076
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
10635
11077
|
|
|
10636
11078
|
// src/version.ts
|
|
10637
|
-
var NATIVE_HOST_VERSION = true ? "2.7.
|
|
11079
|
+
var NATIVE_HOST_VERSION = true ? "2.7.2" : "unknown";
|
|
10638
11080
|
|
|
10639
11081
|
// src/analytics.ts
|
|
10640
11082
|
var MEASUREMENT_ID = "G-W84LJDZ6PH";
|
|
@@ -10659,7 +11101,7 @@ function writeConfig(config2) {
|
|
|
10659
11101
|
}
|
|
10660
11102
|
}
|
|
10661
11103
|
var clientId = null;
|
|
10662
|
-
function createClientId(seed =
|
|
11104
|
+
function createClientId(seed = randomUUID5()) {
|
|
10663
11105
|
let first = 2166136261;
|
|
10664
11106
|
let second = 2246822519;
|
|
10665
11107
|
for (let index = 0; index < seed.length; index++) {
|
|
@@ -31907,11 +32349,6 @@ async function handleViaCdp(action, data, _timeoutMs, controlSource) {
|
|
|
31907
32349
|
`${action} requires the Mearl browser extension. Install it from https://chromewebstore.google.com/detail/mearl/aoehhjnofngknnjefamjbplchbolghkm`
|
|
31908
32350
|
);
|
|
31909
32351
|
}
|
|
31910
|
-
if (AGENTBAY_SESSION_ID && action === "page_upload") {
|
|
31911
|
-
throw new Error(
|
|
31912
|
-
"page_upload is not available for AgentBay CDP sessions because file paths resolve on the remote browser host."
|
|
31913
|
-
);
|
|
31914
|
-
}
|
|
31915
32352
|
const available = AGENTBAY_SESSION_ID ? await getCdpSession().getBrowserClient().then(
|
|
31916
32353
|
() => true,
|
|
31917
32354
|
() => false
|
|
@@ -31926,10 +32363,11 @@ async function handleViaCdp(action, data, _timeoutMs, controlSource) {
|
|
|
31926
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.`
|
|
31927
32364
|
);
|
|
31928
32365
|
}
|
|
31929
|
-
|
|
31930
|
-
|
|
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);
|
|
31931
32369
|
}
|
|
31932
|
-
return dispatchCdpAction(getCdpSession(), action,
|
|
32370
|
+
return dispatchCdpAction(getCdpSession(), action, actionData, controlSource);
|
|
31933
32371
|
}
|
|
31934
32372
|
function onSocketConnection(socket) {
|
|
31935
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",
|