@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 and diagnostic
14
- * deltas. `observe: false` disables DOM/navigation observation;
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 event;
1035
- const { _raw: _discardedRaw, _context, url: url2, ...summary } = event;
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 diagnosticsEnabled = data?.diagnostics !== void 0 && data.diagnostics !== false;
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 || data?.diagnostics !== 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 : action === "page_eval" ? void 0 : DEFAULT_PAGE_DIAGNOSTICS;
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 actionError;
3959
+ let actionFailure;
3756
3960
  try {
3757
3961
  actionResult = await dependencies.executeAction(typedAction, actionData);
3758
3962
  } catch (error48) {
3759
- actionError = error48 instanceof Error ? error48.message : String(error48);
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 && actionError === void 0 && (navigationObserver.hasStarted() || shouldWaitForPotentialNavigation(typedAction, observation))) {
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 && actionError === void 0 && observation.mode !== "navigation" && navigationObserver.hasNetworkActivity()) {
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: actionError === void 0,
4027
+ success: actionFailure === void 0,
3810
4028
  ...actionResult !== void 0 ? { data: actionResult } : {},
3811
- ...actionError ? { error: actionError } : {}
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, sequence) {
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.push(...events);
4751
- if (buffers.events.length > MAX_EVENTS) {
4752
- buffers.events.splice(0, buffers.events.length - MAX_EVENTS);
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 sequence = ++buffers.requestCounter;
4791
- const captured = appendTrackingEvents(buffers, url2, params.request?.postData, sequence);
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((result) => appendTrackingEvents(buffers, url2, result?.postData, sequence)).catch(() => {
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 = queryTrackingEvents(session.buffers.events, {
6598
- source,
6599
- event_type,
6600
- since,
6601
- limit,
6602
- filter
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 actionError;
6882
+ let actionFailure;
6712
6883
  try {
6713
6884
  actionResult = await dispatchCdpAction(session, action, actionData, controlSource);
6714
6885
  } catch (error48) {
6715
- actionError = error48 instanceof Error ? error48.message : String(error48);
6886
+ actionFailure = describePageActionFailure(error48);
6716
6887
  }
6717
- return {
6888
+ const result2 = {
6718
6889
  action: {
6719
6890
  name: action,
6720
- success: actionError === void 0,
6891
+ success: actionFailure === void 0,
6721
6892
  ...actionResult !== void 0 ? { data: actionResult } : {},
6722
- ...actionError ? { error: actionError } : {}
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 Error(`Ref ${ref} not found. Run page_snapshot first to get fresh refs.`);
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 Error(value.error);
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 (!value) {
8299
- throw new Error(
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 Error(value.error);
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 (!value) {
8352
- throw new Error(`Cannot resolve clickable element for backendNodeId ${backendNodeId}`);
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 Error(`page_click: invalid scope "${scope}": ${message}`);
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 Error(`page_click: scope not found: ${scope}`);
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 Error(`page_click: cannot resolve scope: ${scope}`);
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 Error('page_click: "clickMode" must be one of auto, dom, mouse, touch');
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 Error('page_click: "scope" is only supported with the "text" target');
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 Error(
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 Error(
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 Error(
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 Error(
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 Error("page_click point is required");
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 Error(
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
- if (typeof text !== "string") throw new Error('"text" must be a string.');
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 Error(value2.error);
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 Error(value.error);
8710
- if (!value.typed) throw new Error("cdp_page_type: failed to type");
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 Error("Invalid expression");
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 Error("Invalid key");
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 Error('page_upload: "selector" is required (CSS selector or @eN snapshot ref)');
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 Error("Invalid filePaths: must be a non-empty array of file path strings");
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) throw new Error(`Element not found: ${selector}`);
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) throw new Error(`Cannot resolve file input: ${selector}`);
9114
+ if (backendNodeId === void 0) {
9115
+ throw new PageActionPreconditionError(`Cannot resolve file input: ${selector}`);
9116
+ }
8914
9117
  if (tag !== "input") {
8915
- throw new Error(
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 Error('page_hover: "selector" is required (CSS selector or @eN snapshot ref)');
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 randomUUID4 } from "node:crypto";
11076
+ import { randomUUID as randomUUID5 } from "node:crypto";
10789
11077
 
10790
11078
  // src/version.ts
10791
- var NATIVE_HOST_VERSION = true ? "2.7.1" : "unknown";
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 = randomUUID4()) {
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
- if (shouldObservePageAction(action, data)) {
32084
- return cdpObservedPageAction(getCdpSession(), action, data, controlSource);
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, data, controlSource);
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.1",
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.1",
41
- "@mearl/daemon-core": "2.7.1",
42
- "@mearl/setup": "2.7.1"
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",