@mearl/native-host 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -325,7 +325,7 @@ import http3 from "node:http";
325
325
 
326
326
  // src/cdp-backend/client.ts
327
327
  import http from "node:http";
328
- import crypto from "node:crypto";
328
+ import crypto2 from "node:crypto";
329
329
  var CdpChildSession = class {
330
330
  constructor(parent, sessionId) {
331
331
  this.parent = parent;
@@ -406,8 +406,8 @@ var CdpClient = class _CdpClient {
406
406
  return new Promise((resolve, reject) => {
407
407
  const url2 = new URL(wsUrl);
408
408
  const port = parseInt(url2.port || "9222", 10);
409
- const key = crypto.randomBytes(16).toString("base64");
410
- const expectedAccept = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
409
+ const key = crypto2.randomBytes(16).toString("base64");
410
+ const expectedAccept = crypto2.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
411
411
  const req = http.request({
412
412
  host: url2.hostname,
413
413
  port,
@@ -559,7 +559,7 @@ var CdpClient = class _CdpClient {
559
559
  sendText(text) {
560
560
  if (this._closed) return;
561
561
  const payload = Buffer.from(text, "utf-8");
562
- const maskKey = crypto.randomBytes(4);
562
+ const maskKey = crypto2.randomBytes(4);
563
563
  const masked = Buffer.from(payload.map((b, i) => b ^ maskKey[i % 4]));
564
564
  let header;
565
565
  if (payload.length < 126) {
@@ -754,6 +754,9 @@ function buildCookieHeaderValue(cookieMap) {
754
754
  var APLUS_URL_PATTERN = /\.mmstat\.com\//;
755
755
  var RUM_URL_PATTERN = /\.rum\.aliyuncs\.com\//;
756
756
  var ARMS_URL_PATTERN = /arms-retcode\.aliyuncs\.com\//;
757
+ function isTrackingUrl(url2) {
758
+ return APLUS_URL_PATTERN.test(url2) || RUM_URL_PATTERN.test(url2) || ARMS_URL_PATTERN.test(url2) || /retcode\.alicdn\.com|beacon\.taobao\.com|tbsec\./i.test(url2);
759
+ }
757
760
  var aplusCounter = 0;
758
761
  var armsCounter = 0;
759
762
  var rumCounter = 0;
@@ -1287,7 +1290,11 @@ function serializeRequest(request, options = {}) {
1287
1290
  status: request.status,
1288
1291
  statusText: request.statusText,
1289
1292
  queryString: request.queryString,
1290
- duration: request.duration
1293
+ duration: request.duration,
1294
+ captureSequence: request.captureSequence,
1295
+ isPending: request.isPending,
1296
+ source: request.source,
1297
+ resourceType: request.resourceType
1291
1298
  };
1292
1299
  if (includeHeaders) {
1293
1300
  result.headers = request.headers;
@@ -1411,10 +1418,14 @@ function applyFieldLimits(request, maxFieldBytes) {
1411
1418
  request.responseBody = limitBufferedField(request.responseBody, maxFieldBytes);
1412
1419
  }
1413
1420
  function trimRequestBuffer(requests, accounting, maxEntries, maxBytes) {
1421
+ const removed = [];
1414
1422
  while (requests.length > 1 && (requests.length > maxEntries || accounting.totalBytes > maxBytes)) {
1415
- requests.shift();
1423
+ const request = requests.shift();
1424
+ if (request)
1425
+ removed.push(request);
1416
1426
  accounting.totalBytes -= accounting.sizes.shift() ?? 0;
1417
1427
  }
1428
+ return removed;
1418
1429
  }
1419
1430
  function appendRequestToBuffer(requests, accounting, request, limits = {}) {
1420
1431
  const { maxEntries = REQUEST_BUFFER_MAX_ENTRIES, maxBytes = REQUEST_BUFFER_MAX_BYTES, maxFieldBytes = REQUEST_BUFFER_MAX_FIELD_BYTES } = limits;
@@ -1424,20 +1435,20 @@ function appendRequestToBuffer(requests, accounting, request, limits = {}) {
1424
1435
  requests.push(request);
1425
1436
  accounting.sizes.push(size);
1426
1437
  accounting.totalBytes += size;
1427
- trimRequestBuffer(requests, accounting, maxEntries, maxBytes);
1438
+ return trimRequestBuffer(requests, accounting, maxEntries, maxBytes);
1428
1439
  }
1429
1440
  function refreshRequestInBuffer(requests, accounting, request, limits = {}) {
1430
1441
  const { maxEntries = REQUEST_BUFFER_MAX_ENTRIES, maxBytes = REQUEST_BUFFER_MAX_BYTES, maxFieldBytes = REQUEST_BUFFER_MAX_FIELD_BYTES } = limits;
1431
1442
  const index = requests.indexOf(request);
1432
1443
  if (index === -1)
1433
- return;
1444
+ return [];
1434
1445
  synchronizeAccounting(requests, accounting);
1435
1446
  applyFieldLimits(request, maxFieldBytes);
1436
1447
  const previousSize = accounting.sizes[index] ?? 0;
1437
1448
  const nextSize = estimateRequestBufferBytes(request);
1438
1449
  accounting.sizes[index] = nextSize;
1439
1450
  accounting.totalBytes += nextSize - previousSize;
1440
- trimRequestBuffer(requests, accounting, maxEntries, maxBytes);
1451
+ return trimRequestBuffer(requests, accounting, maxEntries, maxBytes);
1441
1452
  }
1442
1453
 
1443
1454
  // ../browser-core/dist/proxyPermissions.js
@@ -3470,6 +3481,7 @@ var PAGE_ACT_ACTIONS = [
3470
3481
  "page_eval"
3471
3482
  ];
3472
3483
  var BROWSER_COMMAND_ACTIONS = {
3484
+ capture_checkpoint: true,
3473
3485
  get_requests: true,
3474
3486
  get_logs: true,
3475
3487
  get_events: true,
@@ -3512,6 +3524,7 @@ var BROWSER_COMMAND_ACTIONS = {
3512
3524
  run_actions: true
3513
3525
  };
3514
3526
  var EXTENSION_BROWSER_ACTIONS = {
3527
+ capture_checkpoint: true,
3515
3528
  get_requests: true,
3516
3529
  get_logs: true,
3517
3530
  get_events: true,
@@ -3558,24 +3571,35 @@ function isExtensionBrowserAction(value) {
3558
3571
  }
3559
3572
 
3560
3573
  // ../browser-core/dist/pageAct.js
3574
+ var DEFAULT_PAGE_DIAGNOSTICS = {
3575
+ logs: true,
3576
+ requests: true
3577
+ };
3561
3578
  function isPageActAction(action) {
3562
3579
  return PAGE_ACT_ACTIONS.includes(action);
3563
3580
  }
3564
3581
  function shouldObservePageAction(action, data) {
3565
3582
  if (!isPageActAction(action))
3566
3583
  return false;
3584
+ const diagnosticsEnabled = data?.diagnostics !== void 0 && data.diagnostics !== false;
3567
3585
  if (action === "page_eval") {
3568
- return Boolean(data?.observe && typeof data.observe === "object" && !Array.isArray(data.observe));
3586
+ return Boolean(diagnosticsEnabled || data?.observe && typeof data.observe === "object" && !Array.isArray(data.observe));
3569
3587
  }
3570
- return data?.observe !== false;
3588
+ return data?.observe !== false || data?.diagnostics !== false;
3571
3589
  }
3572
- function splitObservedActionPayload(data) {
3573
- const { observe, ...actionData } = data ?? {};
3590
+ function splitObservedActionPayload(action, data) {
3591
+ const { observe, diagnostics, ...actionData } = data ?? {};
3592
+ const setting = diagnostics;
3593
+ 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;
3574
3594
  if (observe === false)
3575
- return { actionData, observe: false };
3595
+ return { actionData, observe: false, diagnostics: resolvedDiagnostics };
3596
+ if (action === "page_eval" && (!observe || typeof observe !== "object")) {
3597
+ return { actionData, observe: false, diagnostics: resolvedDiagnostics };
3598
+ }
3576
3599
  return {
3577
3600
  actionData,
3578
- observe: observe && typeof observe === "object" && !Array.isArray(observe) ? observe : {}
3601
+ observe: observe && typeof observe === "object" && !Array.isArray(observe) ? observe : {},
3602
+ diagnostics: resolvedDiagnostics
3579
3603
  };
3580
3604
  }
3581
3605
  function unavailableObservation(checkpoint, error48) {
@@ -4269,6 +4293,20 @@ function assertSendRequestBase64FileLimits(byteLengths) {
4269
4293
  }
4270
4294
  }
4271
4295
 
4296
+ // ../browser-core/dist/sendRequestHeaders.js
4297
+ function resolveHeader(headers, name, fallback) {
4298
+ const key = findSendRequestHeaderName(headers, name);
4299
+ if (!key)
4300
+ return fallback;
4301
+ return headers[key] === "" ? void 0 : headers[key];
4302
+ }
4303
+ function getSendRequestSourceHeaders(targetUrl, headers = {}) {
4304
+ return {
4305
+ referer: resolveHeader(headers, "Referer", `${targetUrl.origin}/mearl`),
4306
+ origin: resolveHeader(headers, "Origin", targetUrl.origin)
4307
+ };
4308
+ }
4309
+
4272
4310
  // ../browser-core/dist/cookie.js
4273
4311
  function normalizeCookieDomain(input, action) {
4274
4312
  const value = input.trim();
@@ -4423,6 +4461,83 @@ ${SOURCE_BADGES[source]}
4423
4461
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
4424
4462
  }
4425
4463
 
4464
+ // ../browser-core/dist/capture.js
4465
+ var CURSOR_PREFIX = "mearl-capture-v1";
4466
+ function createCaptureGeneration() {
4467
+ return crypto.randomUUID();
4468
+ }
4469
+ function encodeCaptureCursor(cursor) {
4470
+ return `${CURSOR_PREFIX}:${cursor.tabId}:${cursor.generation}:${cursor.scope}:${cursor.sequence}`;
4471
+ }
4472
+ function decodeCaptureCursor(value) {
4473
+ const match = value.match(/^mearl-capture-v1:(\d+):([^:]+):(all|logs|requests):(\d+)$/);
4474
+ if (!match)
4475
+ throw new Error("Invalid capture cursor");
4476
+ const tabId = Number(match[1]);
4477
+ const sequence = Number(match[4]);
4478
+ if (!Number.isSafeInteger(tabId) || !Number.isSafeInteger(sequence)) {
4479
+ throw new Error("Invalid capture cursor");
4480
+ }
4481
+ return {
4482
+ tabId,
4483
+ generation: match[2],
4484
+ scope: match[3],
4485
+ sequence
4486
+ };
4487
+ }
4488
+ function captureCheckpoint(state) {
4489
+ return encodeCaptureCursor({
4490
+ tabId: state.tabId,
4491
+ generation: state.generation,
4492
+ sequence: state.currentSequence,
4493
+ scope: "all"
4494
+ });
4495
+ }
4496
+ function nextCaptureSequence(state) {
4497
+ state.currentSequence += 1;
4498
+ return state.currentSequence;
4499
+ }
4500
+ function resolveAfterSequence(after, state, scope) {
4501
+ if (!after)
4502
+ return 0;
4503
+ const cursor = decodeCaptureCursor(after);
4504
+ if (cursor.tabId !== state.tabId) {
4505
+ throw new Error(`Capture cursor belongs to tab ${cursor.tabId}, not tab ${state.tabId}`);
4506
+ }
4507
+ if (cursor.generation !== state.generation) {
4508
+ throw new Error("Capture cursor expired because the tab collector was restarted");
4509
+ }
4510
+ if (cursor.scope !== "all" && cursor.scope !== scope) {
4511
+ throw new Error(`Capture cursor is scoped to ${cursor.scope}, not ${scope}`);
4512
+ }
4513
+ if (cursor.sequence > state.currentSequence) {
4514
+ throw new Error("Capture cursor is ahead of the current collector state");
4515
+ }
4516
+ return cursor.sequence;
4517
+ }
4518
+ function queryCapturedEntries(buffer, state, options, matches, droppedThroughSequence = 0, scope = "all") {
4519
+ const { after, since, now = Date.now() } = options;
4520
+ const limit = Math.max(1, Math.floor(options.limit ?? 100));
4521
+ const afterSequence = resolveAfterSequence(after, state, scope);
4522
+ const sinceTimestampMs = since === void 0 ? void 0 : now - since * 1e3;
4523
+ const matched = buffer.filter((entry) => entry.captureSequence > afterSequence && (sinceTimestampMs === void 0 || entry.timestampMs >= sinceTimestampMs) && matches(entry));
4524
+ const hasMore = matched.length > limit;
4525
+ const entries = after ? matched.slice(0, limit) : matched.slice(-limit);
4526
+ const nextSequence = after && hasMore && entries.length > 0 ? entries[entries.length - 1].captureSequence : state.currentSequence;
4527
+ return {
4528
+ entries,
4529
+ totalMatched: matched.length,
4530
+ nextCursor: encodeCaptureCursor({
4531
+ tabId: state.tabId,
4532
+ generation: state.generation,
4533
+ sequence: nextSequence,
4534
+ scope
4535
+ }),
4536
+ hasMore,
4537
+ gap: after !== void 0 && afterSequence < droppedThroughSequence
4538
+ };
4539
+ }
4540
+
4426
4541
  // src/cdp-backend/domain-manager.ts
4427
4542
  var enabledDomains = /* @__PURE__ */ new WeakMap();
4428
4543
  function ensureCdpDomain(client2, domain2, params = {}) {
@@ -4444,14 +4559,22 @@ function ensureCdpDomain(client2, domain2, params = {}) {
4444
4559
  // src/cdp-backend/buffers.ts
4445
4560
  var MAX_LOGS = 500;
4446
4561
  var MAX_EVENTS = 500;
4447
- function createBuffers() {
4562
+ var COLLECTED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["xhr", "fetch", "document"]);
4563
+ function createBuffers(tabId = 0) {
4448
4564
  return {
4449
4565
  requests: [],
4450
4566
  logs: [],
4451
4567
  events: [],
4452
4568
  pending: /* @__PURE__ */ new Map(),
4453
4569
  requestCounter: 0,
4454
- requestBufferAccounting: createRequestBufferAccounting()
4570
+ requestBufferAccounting: createRequestBufferAccounting(),
4571
+ capture: {
4572
+ tabId,
4573
+ generation: createCaptureGeneration(),
4574
+ currentSequence: 0,
4575
+ droppedRequestsThroughSequence: 0,
4576
+ droppedLogsThroughSequence: 0
4577
+ }
4455
4578
  };
4456
4579
  }
4457
4580
  function parseQueryString(url2) {
@@ -4467,12 +4590,34 @@ function parseQueryString(url2) {
4467
4590
  function headersObjToArray(obj) {
4468
4591
  return Object.entries(obj).map(([name, value]) => ({ name, value }));
4469
4592
  }
4470
- function extractApiName(url2) {
4593
+ function extractMtopApiName(url2, postData) {
4471
4594
  try {
4472
4595
  const u = new URL(url2);
4473
- const match = u.pathname.match(/\/h5\/([^/]+\/[^/]+)/);
4474
- if (match) return match[1];
4475
- return u.pathname;
4596
+ const pathMatch = u.pathname.match(/\/h5\/([\w.]+)\//);
4597
+ if (pathMatch) return pathMatch[1];
4598
+ const requestUrl = u.searchParams.get("requestUrl");
4599
+ const proxyMatch = requestUrl?.match(/\b(?:mtop|mopen)\.[\w.]+/);
4600
+ if (proxyMatch) return proxyMatch[0];
4601
+ } catch {
4602
+ }
4603
+ const urlMatch = url2.match(/\b(?:mtop|mopen)\.[\w.]+/);
4604
+ if (urlMatch) return urlMatch[0];
4605
+ if (!postData) return null;
4606
+ try {
4607
+ const parsed = JSON.parse(postData);
4608
+ const requestUrl = typeof parsed?.requestUrl === "string" ? parsed.requestUrl : void 0;
4609
+ const proxyMatch = requestUrl?.match(/\b(?:mtop|mopen)\.[\w.]+/);
4610
+ if (proxyMatch) return proxyMatch[0];
4611
+ } catch {
4612
+ const requestUrl = new URLSearchParams(postData).get("requestUrl");
4613
+ const proxyMatch = requestUrl?.match(/\b(?:mtop|mopen)\.[\w.]+/);
4614
+ if (proxyMatch) return proxyMatch[0];
4615
+ }
4616
+ return null;
4617
+ }
4618
+ function requestDisplayName(url2) {
4619
+ try {
4620
+ return new URL(url2).pathname;
4476
4621
  } catch {
4477
4622
  return url2;
4478
4623
  }
@@ -4503,28 +4648,73 @@ function appendTrackingEvents(buffers, url2, postData, sequence) {
4503
4648
  }
4504
4649
  return true;
4505
4650
  }
4506
- function setupBufferListeners(client2, buffers) {
4507
- ensureCdpDomain(client2, "Network", {
4508
- maxTotalBufferSize: 10 * 1024 * 1024,
4509
- maxPostDataSize: 5 * 1024 * 1024
4510
- }).catch(() => {
4511
- });
4512
- ensureCdpDomain(client2, "Runtime").catch(() => {
4513
- });
4514
- ensureCdpDomain(client2, "Log").catch(() => {
4515
- });
4651
+ function recordDroppedRequests(buffers, removed) {
4652
+ for (const request of removed) {
4653
+ if (request.captureSequence !== void 0) {
4654
+ buffers.capture.droppedRequestsThroughSequence = Math.max(
4655
+ buffers.capture.droppedRequestsThroughSequence,
4656
+ request.captureSequence
4657
+ );
4658
+ }
4659
+ }
4660
+ }
4661
+ function appendCapturedRequest(buffers, request) {
4662
+ recordDroppedRequests(
4663
+ buffers,
4664
+ appendRequestToBuffer(buffers.requests, buffers.requestBufferAccounting, request)
4665
+ );
4666
+ }
4667
+ function refreshCapturedRequest(buffers, request) {
4668
+ recordDroppedRequests(
4669
+ buffers,
4670
+ refreshRequestInBuffer(buffers.requests, buffers.requestBufferAccounting, request)
4671
+ );
4672
+ }
4673
+ function appendCapturedLog(buffers, log2) {
4674
+ buffers.logs.push({ ...log2, captureSequence: nextCaptureSequence(buffers.capture) });
4675
+ if (buffers.logs.length > MAX_LOGS) {
4676
+ const removed = buffers.logs.shift();
4677
+ if (removed) buffers.capture.droppedLogsThroughSequence = removed.captureSequence;
4678
+ }
4679
+ }
4680
+ async function setupBufferListeners(client2, buffers) {
4516
4681
  client2.on("Network.requestWillBeSent", (params) => {
4517
4682
  const url2 = params.request?.url ?? "";
4518
- const ts = params.timestamp ?? Date.now() / 1e3;
4683
+ if (!url2.startsWith("http")) return;
4684
+ if (isTrackingUrl(url2)) {
4685
+ const sequence = ++buffers.requestCounter;
4686
+ const captured = appendTrackingEvents(buffers, url2, params.request?.postData, sequence);
4687
+ if (!captured && params.request?.hasPostData && !params.request?.postData) {
4688
+ client2.sendCommand("Network.getRequestPostData", { requestId: params.requestId }, 1e4).then((result) => appendTrackingEvents(buffers, url2, result?.postData, sequence)).catch(() => {
4689
+ });
4690
+ }
4691
+ return;
4692
+ }
4693
+ const resourceType = String(params.type ?? "").toLowerCase();
4694
+ const mtopApiName = extractMtopApiName(url2, params.request?.postData);
4695
+ const isMtop = mtopApiName !== null;
4696
+ if (!isMtop && !COLLECTED_RESOURCE_TYPES.has(resourceType)) return;
4697
+ const ts = params.timestamp;
4698
+ const timestampMs2 = params.wallTime !== void 0 ? Math.floor(params.wallTime * 1e3) : Date.now();
4699
+ const redirected = buffers.pending.get(params.requestId);
4700
+ if (redirected && params.redirectResponse) {
4701
+ redirected.status = params.redirectResponse.status ?? 0;
4702
+ redirected.statusText = params.redirectResponse.statusText ?? "";
4703
+ redirected.duration = ts !== void 0 && redirected.startTs !== void 0 ? Math.round((ts - redirected.startTs) * 1e3) : void 0;
4704
+ redirected.isPending = false;
4705
+ refreshCapturedRequest(buffers, redirected);
4706
+ buffers.pending.delete(params.requestId);
4707
+ }
4519
4708
  const id = ++buffers.requestCounter;
4709
+ const captureSequence = nextCaptureSequence(buffers.capture);
4520
4710
  const partial2 = {
4521
4711
  id,
4522
4712
  requestId: params.requestId,
4523
- timestamp: new Date(ts * 1e3).toISOString(),
4524
- timestampMs: Math.floor(ts * 1e3),
4713
+ timestamp: new Date(timestampMs2).toISOString(),
4714
+ timestampMs: timestampMs2,
4525
4715
  method: params.request?.method ?? "GET",
4526
4716
  url: url2,
4527
- apiName: extractApiName(url2),
4717
+ apiName: mtopApiName ?? requestDisplayName(url2),
4528
4718
  headers: headersObjToArray(params.request?.headers ?? {}),
4529
4719
  postData: parsePostData(params.request?.postData),
4530
4720
  queryString: parseQueryString(url2),
@@ -4535,16 +4725,14 @@ function setupBufferListeners(client2, buffers) {
4535
4725
  isProxy: false,
4536
4726
  proxyRequestUrl: null,
4537
4727
  proxyData: null,
4538
- startTs: ts
4728
+ startTs: ts,
4729
+ captureSequence,
4730
+ isPending: true,
4731
+ source: isMtop ? "mtop" : "requests",
4732
+ resourceType
4539
4733
  };
4540
4734
  buffers.pending.set(params.requestId, partial2);
4541
- const captured = appendTrackingEvents(buffers, url2, params.request?.postData, id);
4542
- if (!captured && params.request?.hasPostData && !params.request?.postData) {
4543
- client2.sendCommand("Network.getRequestPostData", { requestId: params.requestId }, 1e4).then((result) => {
4544
- appendTrackingEvents(buffers, url2, result?.postData, id);
4545
- }).catch(() => {
4546
- });
4547
- }
4735
+ appendCapturedRequest(buffers, partial2);
4548
4736
  });
4549
4737
  client2.on("Network.responseReceived", (params) => {
4550
4738
  const partial2 = buffers.pending.get(params.requestId);
@@ -4552,73 +4740,110 @@ function setupBufferListeners(client2, buffers) {
4552
4740
  partial2.status = params.response?.status ?? 0;
4553
4741
  partial2.statusText = params.response?.statusText ?? "";
4554
4742
  partial2.responseHeaders = headersObjToArray(params.response?.headers ?? {});
4743
+ refreshCapturedRequest(buffers, partial2);
4555
4744
  });
4556
4745
  client2.on("Network.loadingFinished", (params) => {
4557
4746
  const partial2 = buffers.pending.get(params.requestId);
4558
4747
  if (!partial2) return;
4559
4748
  buffers.pending.delete(params.requestId);
4560
- const endTs = params.timestamp ?? Date.now() / 1e3;
4561
- const duration3 = Math.round((endTs - partial2.startTs) * 1e3);
4562
- const req = {
4563
- ...partial2,
4564
- duration: duration3,
4565
- isPending: false
4566
- };
4749
+ const endTs = params.timestamp;
4750
+ const duration3 = endTs !== void 0 && partial2.startTs !== void 0 ? Math.round((endTs - partial2.startTs) * 1e3) : void 0;
4751
+ partial2.duration = duration3;
4752
+ partial2.isPending = false;
4753
+ refreshCapturedRequest(buffers, partial2);
4567
4754
  client2.sendCommand("Network.getResponseBody", { requestId: params.requestId }, 1e4).then((result) => {
4568
- req.responseBody = result.base64Encoded ? Buffer.from(result.body, "base64").toString("utf-8") : result.body;
4569
- if (typeof req.responseBody === "string") {
4755
+ partial2.responseBody = result.base64Encoded ? Buffer.from(result.body, "base64").toString("utf-8") : result.body;
4756
+ if (typeof partial2.responseBody === "string") {
4570
4757
  try {
4571
- req.responseBody = JSON.parse(req.responseBody);
4758
+ partial2.responseBody = JSON.parse(partial2.responseBody);
4572
4759
  } catch {
4573
4760
  }
4574
4761
  }
4575
- refreshRequestInBuffer(buffers.requests, buffers.requestBufferAccounting, req);
4762
+ refreshCapturedRequest(buffers, partial2);
4576
4763
  }).catch(() => {
4577
4764
  });
4578
- appendRequestToBuffer(buffers.requests, buffers.requestBufferAccounting, req);
4579
4765
  });
4580
4766
  client2.on("Network.loadingFailed", (params) => {
4581
4767
  const partial2 = buffers.pending.get(params.requestId);
4582
4768
  if (!partial2) return;
4583
4769
  buffers.pending.delete(params.requestId);
4584
- const req = {
4585
- ...partial2,
4586
- status: 0,
4587
- statusText: params.errorText ?? "Failed",
4588
- duration: 0,
4589
- isPending: false
4590
- };
4591
- appendRequestToBuffer(buffers.requests, buffers.requestBufferAccounting, req);
4770
+ partial2.status = 0;
4771
+ partial2.statusText = params.errorText ?? "Failed";
4772
+ partial2.duration = params.timestamp !== void 0 && partial2.startTs !== void 0 ? Math.round((params.timestamp - partial2.startTs) * 1e3) : void 0;
4773
+ partial2.isPending = false;
4774
+ refreshCapturedRequest(buffers, partial2);
4592
4775
  });
4593
4776
  client2.on("Runtime.consoleAPICalled", (params) => {
4594
- const level = params.type ?? "log";
4777
+ const levelMap = {
4778
+ warning: "warn",
4779
+ debug: "debug",
4780
+ info: "info",
4781
+ error: "error",
4782
+ assert: "error"
4783
+ };
4784
+ const level = levelMap[params.type] ?? "log";
4595
4785
  const args = (params.args ?? []).map((arg) => {
4596
4786
  if (arg.type === "string") return arg.value;
4597
4787
  if (arg.value !== void 0) return String(arg.value);
4598
4788
  if (arg.description !== void 0) return arg.description;
4599
4789
  return JSON.stringify(arg);
4600
4790
  });
4601
- const entry = {
4791
+ appendCapturedLog(buffers, {
4602
4792
  level,
4603
4793
  message: args.join(" "),
4604
- timestamp: params.timestamp ? Math.floor(params.timestamp * 1e3) : Date.now(),
4605
- source: "console",
4606
- args
4607
- };
4608
- buffers.logs.push(entry);
4609
- if (buffers.logs.length > MAX_LOGS) buffers.logs.shift();
4794
+ timestampMs: params.timestamp ?? Date.now(),
4795
+ source: params.stackTrace?.callFrames?.[0]?.url ? `${params.stackTrace.callFrames[0].url.split("/").pop() || params.stackTrace.callFrames[0].url}:${params.stackTrace.callFrames[0].lineNumber + 1}` : "console",
4796
+ args,
4797
+ origin: "console"
4798
+ });
4799
+ });
4800
+ client2.on("Runtime.exceptionThrown", (params) => {
4801
+ const details = params.exceptionDetails ?? {};
4802
+ const exception = details.exception;
4803
+ const message = exception?.description ?? (exception?.value !== void 0 ? String(exception.value) : void 0) ?? details.text ?? "Uncaught exception";
4804
+ const topFrame = details.stackTrace?.callFrames?.[0];
4805
+ const url2 = topFrame?.url || details.url;
4806
+ const lineNumber = topFrame?.lineNumber ?? details.lineNumber;
4807
+ const source = url2 ? `${url2.split("/").pop() || url2}${lineNumber !== void 0 ? `:${lineNumber + 1}` : ""}` : void 0;
4808
+ appendCapturedLog(buffers, {
4809
+ level: "error",
4810
+ message,
4811
+ timestampMs: params.timestamp ?? Date.now(),
4812
+ ...source ? { source } : {},
4813
+ args: [message],
4814
+ origin: "exception"
4815
+ });
4610
4816
  });
4611
4817
  client2.on("Log.entryAdded", (params) => {
4612
- const entry = {
4613
- level: params.entry?.level ?? "log",
4614
- message: params.entry?.text ?? "",
4615
- timestamp: params.entry?.timestamp ? Math.floor(params.entry.timestamp * 1e3) : Date.now(),
4616
- source: params.entry?.source ?? "other",
4617
- args: [params.entry?.text ?? ""]
4818
+ const entry = params.entry ?? {};
4819
+ const levelMap = {
4820
+ verbose: "log",
4821
+ info: "info",
4822
+ warning: "warn",
4823
+ error: "error"
4618
4824
  };
4619
- buffers.logs.push(entry);
4620
- if (buffers.logs.length > MAX_LOGS) buffers.logs.shift();
4825
+ const args = (entry.args ?? []).map((arg) => {
4826
+ if (arg.value !== void 0) return String(arg.value);
4827
+ return arg.description ?? `[${arg.type || "Object"}]`;
4828
+ });
4829
+ const message = entry.text || args.join(" ");
4830
+ appendCapturedLog(buffers, {
4831
+ level: levelMap[entry.level] ?? entry.level ?? "log",
4832
+ message,
4833
+ timestampMs: entry.timestamp ?? Date.now(),
4834
+ source: entry.url ? `${entry.url.split("/").pop() || entry.url}${entry.lineNumber !== void 0 ? `:${entry.lineNumber + 1}` : ""}` : entry.source ?? "other",
4835
+ args: args.length > 0 ? args : [message],
4836
+ origin: "browser"
4837
+ });
4621
4838
  });
4839
+ await Promise.all([
4840
+ ensureCdpDomain(client2, "Network", {
4841
+ maxTotalBufferSize: 10 * 1024 * 1024,
4842
+ maxPostDataSize: 5 * 1024 * 1024
4843
+ }),
4844
+ ensureCdpDomain(client2, "Runtime"),
4845
+ ensureCdpDomain(client2, "Log")
4846
+ ]);
4622
4847
  }
4623
4848
 
4624
4849
  // src/cdp-backend/discover.ts
@@ -4777,9 +5002,21 @@ var CdpSession = class {
4777
5002
  } catch {
4778
5003
  client2 = await CdpClient.connect(target.webSocketDebuggerUrl);
4779
5004
  }
4780
- const buffers = createBuffers();
4781
- setupBufferListeners(client2, buffers);
5005
+ let tabId;
5006
+ for (const [virtualId, registeredTargetId] of this.tabIdMap) {
5007
+ if (registeredTargetId === target.id) {
5008
+ tabId = virtualId;
5009
+ break;
5010
+ }
5011
+ }
5012
+ if (tabId === void 0) {
5013
+ tabId = this.nextTabId++;
5014
+ this.tabIdMap.set(tabId, target.id);
5015
+ }
5016
+ const buffers = createBuffers(tabId);
5017
+ await setupBufferListeners(client2, buffers);
4782
5018
  const session = {
5019
+ tabId,
4783
5020
  client: client2,
4784
5021
  buffers,
4785
5022
  targetId: target.id,
@@ -6035,6 +6272,143 @@ async function enrichCdpPageChangeSetWithRefs(session, observation) {
6035
6272
  );
6036
6273
  }
6037
6274
 
6275
+ // src/cdp-backend/request-handlers.ts
6276
+ function filterRequests(buffers, opts) {
6277
+ const pattern = opts.filter ? compileTextPattern(opts.filter) : void 0;
6278
+ return queryCapturedEntries(
6279
+ buffers.requests.filter(
6280
+ (request) => Number.isSafeInteger(request.captureSequence)
6281
+ ),
6282
+ buffers.capture,
6283
+ { after: opts.after, since: opts.since, limit: opts.count ?? 10 },
6284
+ (request) => (!opts.source || opts.source === "all" || request.source === opts.source) && (!pattern || matchesTextPattern(request.url, pattern) || matchesTextPattern(request.apiName, pattern)),
6285
+ buffers.capture.droppedRequestsThroughSequence,
6286
+ "requests"
6287
+ );
6288
+ }
6289
+ async function cdpGetRequests(session, data) {
6290
+ const opts = {
6291
+ count: data.count ?? 10,
6292
+ filter: data.filter,
6293
+ since: data.since,
6294
+ after: data.after,
6295
+ source: data.source,
6296
+ applyHiddenFilter: data.applyHiddenFilter !== false
6297
+ };
6298
+ const captured = filterRequests(session.buffers, opts);
6299
+ const serializeOpts = {
6300
+ includeBody: data.includeBody !== false,
6301
+ includeHeaders: data.includeHeaders === true,
6302
+ maxBodySize: data.maxBodySize ?? 5e4,
6303
+ fields: data.fields,
6304
+ keysOnly: data.keysOnly === true
6305
+ };
6306
+ return {
6307
+ meta: {
6308
+ count: captured.entries.length,
6309
+ totalMatched: captured.totalMatched,
6310
+ totalInBuffer: session.buffers.requests.length,
6311
+ filter: data.filter,
6312
+ since: data.since,
6313
+ after: data.after,
6314
+ nextCursor: captured.nextCursor,
6315
+ hasMore: captured.hasMore,
6316
+ gap: captured.gap,
6317
+ source: "cdp"
6318
+ },
6319
+ requests: captured.entries.map((r) => serializeRequest(r, serializeOpts))
6320
+ };
6321
+ }
6322
+ async function cdpGetLogs(session, data) {
6323
+ const { level, since, after, limit = 100, filter } = data;
6324
+ const pattern = filter ? compileTextPattern(filter) : void 0;
6325
+ const captured = queryCapturedEntries(
6326
+ session.buffers.logs,
6327
+ session.buffers.capture,
6328
+ { after, since, limit },
6329
+ (log2) => (!level || level === "all" || log2.level === level) && (!pattern || matchesTextPattern(log2.message, pattern) || log2.args.some((arg) => matchesTextPattern(arg, pattern))),
6330
+ session.buffers.capture.droppedLogsThroughSequence,
6331
+ "logs"
6332
+ );
6333
+ const captureStatus = session.buffers.logs.length === 0 ? "armed" : "active";
6334
+ return {
6335
+ meta: {
6336
+ count: captured.entries.length,
6337
+ totalMatched: captured.totalMatched,
6338
+ totalInBuffer: session.buffers.logs.length,
6339
+ level: level || "all",
6340
+ since,
6341
+ after,
6342
+ filter,
6343
+ nextCursor: captured.nextCursor,
6344
+ hasMore: captured.hasMore,
6345
+ gap: captured.gap,
6346
+ captureStatus,
6347
+ ...captureStatus === "armed" && {
6348
+ hint: "Log capture is active, but this tab has no captured history yet. Reload the current tab or repeat the triggering action, then call get_logs again."
6349
+ },
6350
+ source: "cdp"
6351
+ },
6352
+ logs: captured.entries
6353
+ };
6354
+ }
6355
+ async function cdpCaptureCheckpoint(session, _data) {
6356
+ return {
6357
+ tabId: session.tabId,
6358
+ cursor: captureCheckpoint(session.buffers.capture),
6359
+ timestampMs: Date.now(),
6360
+ captureStatus: session.buffers.capture.currentSequence === 0 ? "armed" : "active"
6361
+ };
6362
+ }
6363
+ async function collectCdpPageDiagnostics(session, after, diagnostics) {
6364
+ const result = {};
6365
+ if (diagnostics.logs) {
6366
+ const options = diagnostics.logs === true ? { level: "error", limit: 100 } : { level: "error", limit: 100, ...diagnostics.logs };
6367
+ result.logs = await cdpGetLogs(session, { ...options, after });
6368
+ }
6369
+ if (diagnostics.requests) {
6370
+ const options = diagnostics.requests === true ? { source: "all", includeBody: false, count: 100 } : {
6371
+ source: "all",
6372
+ includeBody: false,
6373
+ count: 100,
6374
+ ...diagnostics.requests
6375
+ };
6376
+ result.requests = await cdpGetRequests(session, { ...options, after });
6377
+ }
6378
+ return result;
6379
+ }
6380
+ async function cdpGetEvents(session, data) {
6381
+ const { source = "all", event_type, since, limit = 20, filter, includeRaw = false } = data;
6382
+ const response = queryTrackingEvents(session.buffers.events, {
6383
+ source,
6384
+ event_type,
6385
+ since,
6386
+ limit,
6387
+ filter
6388
+ });
6389
+ const captureStatus = response.totalInBuffer === 0 ? "armed" : "active";
6390
+ const hint = response.events.length === 0 && captureStatus === "armed" ? TRACKING_CAPTURE_ARMED_HINT : void 0;
6391
+ return {
6392
+ meta: {
6393
+ count: response.events.length,
6394
+ totalMatched: response.totalMatched,
6395
+ totalInBuffer: response.totalInBuffer,
6396
+ source,
6397
+ captureStatus,
6398
+ captureSource: "cdp",
6399
+ includeRaw,
6400
+ hint
6401
+ },
6402
+ events: response.events.map((event) => formatTrackingEvent(event, includeRaw))
6403
+ };
6404
+ }
6405
+ registerActions([
6406
+ { name: "capture_checkpoint", session: "page", handler: cdpCaptureCheckpoint },
6407
+ { name: "get_requests", session: "page", handler: cdpGetRequests },
6408
+ { name: "get_logs", session: "page", handler: cdpGetLogs },
6409
+ { name: "get_events", session: "page", handler: cdpGetEvents }
6410
+ ]);
6411
+
6038
6412
  // src/cdp-backend/composite-handlers.ts
6039
6413
  async function cdpRunActions(session, data, controlSource = "local") {
6040
6414
  const { actions, tabId } = data;
@@ -6110,21 +6484,42 @@ async function cdpRunActions(session, data, controlSource = "local") {
6110
6484
  };
6111
6485
  }
6112
6486
  async function cdpObservedPageAction(session, action, data, controlSource = "local") {
6113
- const { actionData, observe } = splitObservedActionPayload(data);
6114
- if (observe === false) {
6115
- return dispatchCdpAction(session, action, actionData, controlSource);
6116
- }
6487
+ const { actionData, observe, diagnostics } = splitObservedActionPayload(action, data);
6117
6488
  const { tabId } = actionData;
6118
6489
  if (typeof tabId !== "number") throw new Error(`${action} requires a numeric tabId`);
6119
6490
  const pageSession = await session.getPageSession(tabId);
6120
6491
  await ensureControlledIndicator(pageSession, controlSource);
6121
- return runPageAct(
6492
+ if (observe === false) {
6493
+ if (!diagnostics) return dispatchCdpAction(session, action, actionData, controlSource);
6494
+ const cursor = captureCheckpoint(pageSession.buffers.capture);
6495
+ let actionResult;
6496
+ let actionError;
6497
+ try {
6498
+ actionResult = await dispatchCdpAction(session, action, actionData, controlSource);
6499
+ } catch (error48) {
6500
+ actionError = error48 instanceof Error ? error48.message : String(error48);
6501
+ }
6502
+ return {
6503
+ action: {
6504
+ name: action,
6505
+ success: actionError === void 0,
6506
+ ...actionResult !== void 0 ? { data: actionResult } : {},
6507
+ ...actionError ? { error: actionError } : {}
6508
+ },
6509
+ diagnostics: await collectCdpPageDiagnostics(pageSession, cursor, diagnostics)
6510
+ };
6511
+ }
6512
+ let diagnosticsCursor;
6513
+ const result = await runPageAct(
6122
6514
  { action, actionData, observe },
6123
6515
  {
6124
6516
  startObservation: () => startPageObservation(pageSession),
6125
6517
  finishObservation: (checkpoint, options) => finishPageObservation(pageSession, checkpoint, options),
6126
6518
  startNavigationObserver: (options) => startCdpNavigationObserver(session, pageSession, options.urlBefore, options),
6127
- executeAction: (name, params) => dispatchCdpAction(session, name, { ...params, tabId }, controlSource),
6519
+ executeAction: (name, params) => {
6520
+ if (diagnostics) diagnosticsCursor = captureCheckpoint(pageSession.buffers.capture);
6521
+ return dispatchCdpAction(session, name, { ...params, tabId }, controlSource);
6522
+ },
6128
6523
  finalizeObservation: (observation) => {
6129
6524
  if (observation.mode === "delta") {
6130
6525
  return enrichCdpPageChangeSetWithRefs(pageSession, observation);
@@ -6134,6 +6529,11 @@ async function cdpObservedPageAction(session, action, data, controlSource = "loc
6134
6529
  disposeObservation: () => disposePageObservation(pageSession)
6135
6530
  }
6136
6531
  );
6532
+ if (!diagnostics || !diagnosticsCursor) return result;
6533
+ return {
6534
+ ...result,
6535
+ diagnostics: await collectCdpPageDiagnostics(pageSession, diagnosticsCursor, diagnostics)
6536
+ };
6137
6537
  }
6138
6538
  registerActions([{ name: "run_actions", session: "browser", handler: cdpRunActions }]);
6139
6539
 
@@ -6721,112 +7121,6 @@ function listCloudConnectors() {
6721
7121
  return { connectors: pushIfChanged() };
6722
7122
  }
6723
7123
 
6724
- // src/cdp-backend/request-handlers.ts
6725
- function filterRequests(buffers, opts) {
6726
- let requests = [...buffers.requests];
6727
- if (opts.since !== void 0) {
6728
- requests = requests.filter((r) => r.timestampMs >= opts.since);
6729
- }
6730
- if (opts.filter) {
6731
- const pattern = compileTextPattern(opts.filter);
6732
- requests = requests.filter(
6733
- (r) => matchesTextPattern(r.url, pattern) || matchesTextPattern(r.apiName, pattern)
6734
- );
6735
- }
6736
- if (opts.source === "mtop") {
6737
- requests = requests.filter((r) => r.url?.includes("/h5/") || r.url?.includes("mtop"));
6738
- }
6739
- const count = opts.count ?? 10;
6740
- return {
6741
- requests: requests.slice(-count),
6742
- totalMatched: requests.length
6743
- };
6744
- }
6745
- async function cdpGetRequests(session, data) {
6746
- const opts = {
6747
- count: data.count ?? 10,
6748
- filter: data.filter,
6749
- since: data.since,
6750
- source: data.source,
6751
- applyHiddenFilter: data.applyHiddenFilter !== false
6752
- };
6753
- const { requests, totalMatched } = filterRequests(session.buffers, opts);
6754
- const serializeOpts = {
6755
- includeBody: data.includeBody !== false,
6756
- includeHeaders: data.includeHeaders === true,
6757
- maxBodySize: data.maxBodySize ?? 5e4,
6758
- fields: data.fields,
6759
- keysOnly: data.keysOnly === true
6760
- };
6761
- return {
6762
- meta: {
6763
- count: requests.length,
6764
- totalMatched,
6765
- totalInBuffer: session.buffers.requests.length,
6766
- filter: data.filter,
6767
- since: data.since,
6768
- source: "cdp"
6769
- },
6770
- requests: requests.map((r) => serializeRequest(r, serializeOpts))
6771
- };
6772
- }
6773
- async function cdpGetLogs(session, data) {
6774
- const { level, since, limit = 100, filter } = data;
6775
- let logs = [...session.buffers.logs];
6776
- if (since !== void 0) {
6777
- logs = logs.filter((l) => l.timestamp >= since);
6778
- }
6779
- if (level && level !== "all") {
6780
- logs = logs.filter((l) => l.level === level);
6781
- }
6782
- if (filter) {
6783
- const f = filter.toLowerCase();
6784
- logs = logs.filter((l) => l.message?.toLowerCase().includes(f));
6785
- }
6786
- logs = logs.slice(-limit);
6787
- return {
6788
- meta: {
6789
- count: logs.length,
6790
- totalMatched: logs.length,
6791
- level: level || "all",
6792
- since,
6793
- filter,
6794
- source: "cdp"
6795
- },
6796
- logs
6797
- };
6798
- }
6799
- async function cdpGetEvents(session, data) {
6800
- const { source = "all", event_type, since, limit = 20, filter, includeRaw = false } = data;
6801
- const response = queryTrackingEvents(session.buffers.events, {
6802
- source,
6803
- event_type,
6804
- since,
6805
- limit,
6806
- filter
6807
- });
6808
- const captureStatus = response.totalInBuffer === 0 ? "armed" : "active";
6809
- const hint = response.events.length === 0 && captureStatus === "armed" ? TRACKING_CAPTURE_ARMED_HINT : void 0;
6810
- return {
6811
- meta: {
6812
- count: response.events.length,
6813
- totalMatched: response.totalMatched,
6814
- totalInBuffer: response.totalInBuffer,
6815
- source,
6816
- captureStatus,
6817
- captureSource: "cdp",
6818
- includeRaw,
6819
- hint
6820
- },
6821
- events: response.events.map((event) => formatTrackingEvent(event, includeRaw))
6822
- };
6823
- }
6824
- registerActions([
6825
- { name: "get_requests", session: "page", handler: cdpGetRequests },
6826
- { name: "get_logs", session: "page", handler: cdpGetLogs },
6827
- { name: "get_events", session: "page", handler: cdpGetEvents }
6828
- ]);
6829
-
6830
7124
  // src/cdp-backend/send-request.ts
6831
7125
  import { readFile } from "node:fs/promises";
6832
7126
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -6836,6 +7130,10 @@ function setHeader(headers, name, value) {
6836
7130
  if (existing && existing !== name) delete headers[existing];
6837
7131
  headers[name] = value;
6838
7132
  }
7133
+ function removeHeader(headers, name) {
7134
+ const existing = findSendRequestHeaderName(headers, name);
7135
+ if (existing) delete headers[existing];
7136
+ }
6839
7137
  function escapeMultipartQuotedValue(value) {
6840
7138
  return value.replace(/\\/g, "\\\\").replace(/"/g, "%22");
6841
7139
  }
@@ -6944,10 +7242,19 @@ async function executeNodeSendRequest(data, cookieMap = /* @__PURE__ */ new Map(
6944
7242
  Accept: "application/json, text/plain, */*",
6945
7243
  "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
6946
7244
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6947
- Referer: targetUrl.origin + "/mearl",
6948
- Origin: targetUrl.origin,
6949
7245
  ...headers
6950
7246
  };
7247
+ const resolvedSourceHeaders = getSendRequestSourceHeaders(targetUrl, headers);
7248
+ if (resolvedSourceHeaders.referer) {
7249
+ setHeader(requestHeaders, "Referer", resolvedSourceHeaders.referer);
7250
+ } else {
7251
+ removeHeader(requestHeaders, "Referer");
7252
+ }
7253
+ if (resolvedSourceHeaders.origin) {
7254
+ setHeader(requestHeaders, "Origin", resolvedSourceHeaders.origin);
7255
+ } else {
7256
+ removeHeader(requestHeaders, "Origin");
7257
+ }
6951
7258
  const cookieHeader = [...cookieMap.entries()].map(([key, value]) => `${key}=${value}`).join("; ");
6952
7259
  if (cookieHeader) setHeader(requestHeaders, "Cookie", cookieHeader);
6953
7260
  const xsrfToken = cookieMap.get("XSRF-TOKEN");
@@ -7574,6 +7881,14 @@ async function cdpTdbankAccount(cdpSession2, data) {
7574
7881
  registerActions([{ name: "tdbank_account", session: "browser", handler: cdpTdbankAccount }]);
7575
7882
 
7576
7883
  // src/cdp-backend/tab-handlers.ts
7884
+ function currentCapture(pageSession) {
7885
+ return {
7886
+ tabId: pageSession.tabId,
7887
+ cursor: captureCheckpoint(pageSession.buffers.capture),
7888
+ timestampMs: Date.now(),
7889
+ captureStatus: pageSession.buffers.capture.currentSequence === 0 ? "armed" : "active"
7890
+ };
7891
+ }
7577
7892
  async function cdpTabOpen(session, data, controlSource = "local") {
7578
7893
  const { url: url2, active = false, emulation, reuse = "never", match } = data;
7579
7894
  if (!url2 || typeof url2 !== "string") {
@@ -7600,12 +7915,14 @@ async function cdpTabOpen(session, data, controlSource = "local") {
7600
7915
  const tabId2 = await session.ensureTargetRegistered(existing.targetId);
7601
7916
  const pageSession2 = await session.getPageSession(tabId2);
7602
7917
  await ensureControlledIndicator(pageSession2, controlSource);
7918
+ const capture2 = currentCapture(pageSession2);
7603
7919
  return {
7604
7920
  tabId: tabId2,
7605
7921
  url: existing.url ?? url2,
7606
7922
  title: existing.title ?? "",
7607
7923
  opened: false,
7608
- reused: true
7924
+ reused: true,
7925
+ capture: capture2
7609
7926
  };
7610
7927
  }
7611
7928
  if (reuse === "require") {
@@ -7620,6 +7937,7 @@ async function cdpTabOpen(session, data, controlSource = "local") {
7620
7937
  const tabId = await session.registerTarget(targetId);
7621
7938
  const pageSession = await session.getPageSession(tabId);
7622
7939
  await ensureControlledIndicator(pageSession, controlSource);
7940
+ const capture = currentCapture(pageSession);
7623
7941
  if (emulationConfig) {
7624
7942
  for (const { method, params } of buildEnableEmulationCommands(emulationConfig)) {
7625
7943
  await pageSession.client.sendCommand(method, params);
@@ -7638,6 +7956,7 @@ async function cdpTabOpen(session, data, controlSource = "local") {
7638
7956
  title: targetInfo?.title ?? "",
7639
7957
  opened: true,
7640
7958
  reused: false,
7959
+ capture,
7641
7960
  ...emulationConfig && {
7642
7961
  emulation: {
7643
7962
  preset: emulationConfig.presetName,
@@ -9433,7 +9752,7 @@ import os2 from "node:os";
9433
9752
  import { randomUUID as randomUUID4 } from "node:crypto";
9434
9753
 
9435
9754
  // src/version.ts
9436
- var NATIVE_HOST_VERSION = true ? "2.2.2" : "unknown";
9755
+ var NATIVE_HOST_VERSION = true ? "2.3.0" : "unknown";
9437
9756
 
9438
9757
  // src/analytics.ts
9439
9758
  var MEASUREMENT_ID = "G-W84LJDZ6PH";
@@ -10294,13 +10613,13 @@ var AgentChatManager = class {
10294
10613
 
10295
10614
  // src/agent/codex-provider.ts
10296
10615
  import os3 from "node:os";
10297
- import path7 from "node:path";
10298
10616
  import readline from "node:readline";
10299
10617
  import { spawn as spawn6 } from "node:child_process";
10300
10618
 
10301
10619
  // src/agent/executable-resolver.ts
10302
10620
  import fs6 from "node:fs";
10303
10621
  import { spawn as spawn5 } from "node:child_process";
10622
+ import path7 from "node:path";
10304
10623
 
10305
10624
  // ../setup/dist/process.js
10306
10625
  import fs5 from "node:fs";
@@ -10317,6 +10636,27 @@ function isExecutable(filePath, platform = process.platform) {
10317
10636
  function executableNames(name, platform = process.platform) {
10318
10637
  return platform === "win32" ? [`${name}.cmd`, `${name}.exe`, name] : [name];
10319
10638
  }
10639
+ function versionManagerExecutableCandidates(home, names, options = {}) {
10640
+ const env = options.env ?? process.env;
10641
+ const platform = options.platform ?? process.platform;
10642
+ const readdirSync2 = options.readdirSync ?? fs5.readdirSync;
10643
+ const fnmDefaultRoot = env.XDG_DATA_HOME ? path6.join(env.XDG_DATA_HOME, "fnm") : platform === "darwin" ? path6.join(home, "Library", "Application Support", "fnm") : platform === "win32" && env.APPDATA ? path6.join(env.APPDATA, "fnm") : path6.join(home, ".local", "share", "fnm");
10644
+ const roots = [
10645
+ path6.join(env.NVM_DIR || path6.join(home, ".nvm"), "versions", "node"),
10646
+ path6.join(env.FNM_DIR || fnmDefaultRoot, "node-versions"),
10647
+ path6.join(home, ".fnm", "node-versions")
10648
+ ].filter((root, index, all) => all.indexOf(root) === index);
10649
+ return roots.flatMap((root) => {
10650
+ try {
10651
+ return readdirSync2(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).flatMap((entry) => names.flatMap((name) => [
10652
+ path6.join(root, entry.name, "bin", name),
10653
+ path6.join(root, entry.name, "installation", "bin", name)
10654
+ ]));
10655
+ } catch {
10656
+ return [];
10657
+ }
10658
+ });
10659
+ }
10320
10660
  function resolveSpawnCommand(executable, args, platform = process.platform, commandInterpreter = process.env.ComSpec) {
10321
10661
  if (platform === "win32" && /\.(?:cmd|bat)$/i.test(executable)) {
10322
10662
  return {
@@ -10344,6 +10684,46 @@ function prependPath(directory, env) {
10344
10684
  }
10345
10685
 
10346
10686
  // src/agent/executable-resolver.ts
10687
+ function agentExecutableCandidatePaths({
10688
+ executableNames: executableNames2,
10689
+ configuredPath,
10690
+ env = process.env,
10691
+ extraUserCandidates = [],
10692
+ home,
10693
+ platform = process.platform,
10694
+ processExecPath = process.execPath,
10695
+ readdirSync: readdirSync2
10696
+ }) {
10697
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") || "PATH";
10698
+ const pathCandidates = (env[pathKey] || "").split(path7.delimiter).filter(Boolean).flatMap((directory) => executableNames2.map((name) => path7.join(directory, name)));
10699
+ const commonUserDirectories = [
10700
+ path7.join(home, ".local", "bin"),
10701
+ path7.join(home, ".npm-global", "bin"),
10702
+ path7.join(home, ".volta", "bin"),
10703
+ path7.join(home, ".local", "share", "pnpm"),
10704
+ path7.join(home, "Library", "pnpm"),
10705
+ path7.join(home, ".bun", "bin"),
10706
+ ...platform === "win32" && env.APPDATA ? [path7.join(env.APPDATA, "npm")] : []
10707
+ ];
10708
+ const systemDirectories = platform === "win32" ? [] : ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin"];
10709
+ return [
10710
+ configuredPath,
10711
+ ...executableNames2.map((name) => path7.join(path7.dirname(processExecPath), name)),
10712
+ ...commonUserDirectories.flatMap(
10713
+ (directory) => executableNames2.map((name) => path7.join(directory, name))
10714
+ ),
10715
+ ...extraUserCandidates,
10716
+ ...versionManagerExecutableCandidates(home, executableNames2, {
10717
+ env,
10718
+ platform,
10719
+ readdirSync: readdirSync2
10720
+ }),
10721
+ ...systemDirectories.flatMap(
10722
+ (directory) => executableNames2.map((name) => path7.join(directory, name))
10723
+ ),
10724
+ ...pathCandidates
10725
+ ].filter((candidate) => typeof candidate === "string");
10726
+ }
10347
10727
  var AgentExecutableResolver = class {
10348
10728
  constructor(options) {
10349
10729
  this.options = options;
@@ -10457,6 +10837,21 @@ var REQUEST_TIMEOUT_MS = 3e4;
10457
10837
  var PROBE_TIMEOUT_MS = 5e3;
10458
10838
  var MAX_MODEL_CHARS = 256;
10459
10839
  var CODEX_NOT_FOUND_ERROR = "Codex CLI was not found. Install @openai/codex or set MEARL_CODEX_PATH.";
10840
+ function codexExecutableCandidatePaths(options = {}) {
10841
+ const env = options.env ?? process.env;
10842
+ const home = (options.homedir ?? os3.homedir)();
10843
+ const platform = options.platform ?? process.platform;
10844
+ const executableName = platform === "win32" ? "codex.cmd" : "codex";
10845
+ return agentExecutableCandidatePaths({
10846
+ executableNames: [executableName],
10847
+ configuredPath: env.MEARL_CODEX_PATH,
10848
+ env,
10849
+ home,
10850
+ platform,
10851
+ processExecPath: options.processExecPath,
10852
+ readdirSync: options.readdirSync
10853
+ });
10854
+ }
10460
10855
  function codexModelInfo(value) {
10461
10856
  if (typeof value !== "string") return void 0;
10462
10857
  const id = value.trim().slice(0, MAX_MODEL_CHARS);
@@ -10938,18 +11333,7 @@ var CodexAgentProvider = class {
10938
11333
  return this.executableResolver.resolve();
10939
11334
  }
10940
11335
  resolveExecutableCandidates() {
10941
- const executableName = process.platform === "win32" ? "codex.cmd" : "codex";
10942
- const configured = process.env.MEARL_CODEX_PATH;
10943
- const pathCandidates = (process.env.PATH || "").split(path7.delimiter).filter(Boolean).map((dir) => path7.join(dir, executableName));
10944
- return executableFileCandidates([
10945
- configured,
10946
- path7.join(path7.dirname(process.execPath), executableName),
10947
- path7.join(os3.homedir(), ".local", "bin", executableName),
10948
- path7.join(os3.homedir(), ".npm-global", "bin", executableName),
10949
- path7.join(os3.homedir(), ".volta", "bin", executableName),
10950
- ...process.platform === "win32" ? [] : ["/opt/homebrew/bin/codex", "/usr/local/bin/codex", "/usr/bin/codex"],
10951
- ...pathCandidates
10952
- ]);
11336
+ return executableFileCandidates(codexExecutableCandidatePaths());
10953
11337
  }
10954
11338
  };
10955
11339
 
@@ -28303,10 +28687,10 @@ function parseQoderSkillsList(output, cwd, home = os4.homedir()) {
28303
28687
  finish();
28304
28688
  return skills;
28305
28689
  }
28306
- function qoderManagedExecutableCandidates(home) {
28690
+ function qoderManagedExecutableCandidates(home, readdirSync2 = fs7.readdirSync) {
28307
28691
  const directory = path8.join(home, ".qoder", "bin", "qodercli");
28308
28692
  try {
28309
- return fs7.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith("qodercli-")).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).map((entry) => path8.join(directory, entry.name));
28693
+ return readdirSync2(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith("qodercli-")).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).map((entry) => path8.join(directory, entry.name));
28310
28694
  } catch {
28311
28695
  return [];
28312
28696
  }
@@ -28321,6 +28705,25 @@ function isPermissionResponse(value) {
28321
28705
  function qoderInstallCommand(platform = process.platform) {
28322
28706
  return platform === "win32" ? "npm install -g @qoder-ai/qodercli" : "curl -fsSL https://qoder.com/install | bash";
28323
28707
  }
28708
+ function qoderExecutableCandidatePaths(options = {}) {
28709
+ const env = options.env ?? process.env;
28710
+ const home = (options.homedir ?? os4.homedir)();
28711
+ const platform = options.platform ?? process.platform;
28712
+ const executableNames2 = platform === "win32" ? ["qodercli.exe", "qodercli.cmd"] : ["qodercli"];
28713
+ return agentExecutableCandidatePaths({
28714
+ executableNames: executableNames2,
28715
+ configuredPath: env.MEARL_QODER_PATH,
28716
+ env,
28717
+ extraUserCandidates: [
28718
+ ...qoderManagedExecutableCandidates(home, options.readdirSync),
28719
+ ...executableNames2.map((name) => path8.join(home, ".qoder", "bin", name))
28720
+ ],
28721
+ home,
28722
+ platform,
28723
+ processExecPath: options.processExecPath,
28724
+ readdirSync: options.readdirSync
28725
+ });
28726
+ }
28324
28727
  function sessionOptions(options) {
28325
28728
  const cwd = path8.resolve(options.cwd || os4.homedir());
28326
28729
  return {
@@ -29112,19 +29515,7 @@ ${message}` : message;
29112
29515
  return this.executableResolver.resolve();
29113
29516
  }
29114
29517
  resolveExecutableCandidates() {
29115
- const executableNames2 = process.platform === "win32" ? ["qodercli.exe", "qodercli.cmd"] : ["qodercli"];
29116
- const configured = process.env.MEARL_QODER_PATH;
29117
- const home = os4.homedir();
29118
- const pathCandidates = (process.env.PATH || "").split(path8.delimiter).filter(Boolean).flatMap((dir) => executableNames2.map((name) => path8.join(dir, name)));
29119
- return executableFileCandidates([
29120
- configured,
29121
- ...executableNames2.map((name) => path8.join(path8.dirname(process.execPath), name)),
29122
- ...executableNames2.map((name) => path8.join(home, ".local", "bin", name)),
29123
- ...qoderManagedExecutableCandidates(home),
29124
- ...executableNames2.map((name) => path8.join(home, ".qoder", "bin", name)),
29125
- ...process.platform === "win32" ? [] : ["/opt/homebrew/bin/qodercli", "/usr/local/bin/qodercli", "/usr/bin/qodercli"],
29126
- ...pathCandidates
29127
- ]);
29518
+ return executableFileCandidates(qoderExecutableCandidatePaths());
29128
29519
  }
29129
29520
  emit(event) {
29130
29521
  for (const listener of this.listeners) listener(event);
@@ -29142,22 +29533,6 @@ var ANSI_ESCAPE_PATTERN = new RegExp(String.raw`\u001B\[[0-?]*[ -/]*[@-~]`, "g")
29142
29533
  function existingExecutable(candidates, existsSync4) {
29143
29534
  return candidates.find((candidate) => candidate && existsSync4(candidate)) ?? null;
29144
29535
  }
29145
- function versionManagerCandidates(home, executableNames2, readdirSync2) {
29146
- const roots = [
29147
- path9.join(home, ".nvm", "versions", "node"),
29148
- path9.join(home, ".fnm", "node-versions")
29149
- ];
29150
- return roots.flatMap((root) => {
29151
- try {
29152
- return readdirSync2(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((left, right) => right.name.localeCompare(left.name, void 0, { numeric: true })).flatMap((entry) => executableNames2.flatMap((name) => [
29153
- path9.join(root, entry.name, "bin", name),
29154
- path9.join(root, entry.name, "installation", "bin", name)
29155
- ]));
29156
- } catch {
29157
- return [];
29158
- }
29159
- });
29160
- }
29161
29536
  function stripAnsi(value) {
29162
29537
  return value.replace(ANSI_ESCAPE_PATTERN, "");
29163
29538
  }
@@ -29179,7 +29554,11 @@ function resolveAliSkillsExecutable(options = {}) {
29179
29554
  ...names.map((name) => path9.join(home, ".volta", "bin", name)),
29180
29555
  ...names.map((name) => path9.join(home, ".local", "share", "pnpm", name)),
29181
29556
  ...names.map((name) => path9.join(home, "Library", "pnpm", name)),
29182
- ...versionManagerCandidates(home, names, readdirSync2),
29557
+ ...versionManagerExecutableCandidates(home, names, {
29558
+ env,
29559
+ platform,
29560
+ readdirSync: readdirSync2
29561
+ }),
29183
29562
  ...platform === "win32" ? [] : ["/opt/homebrew/bin/ali-skills", "/usr/local/bin/ali-skills", "/usr/bin/ali-skills"],
29184
29563
  ...pathCandidates
29185
29564
  ];