@browserbridge/bbx 1.7.7 → 1.8.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.
Files changed (34) hide show
  1. package/PRIVACY.md +111 -0
  2. package/README.md +13 -5
  3. package/RESPONSIBLE_USE.md +16 -0
  4. package/SECURITY.md +52 -0
  5. package/package.json +12 -6
  6. package/packages/agent-client/src/cli-args.js +24 -0
  7. package/packages/agent-client/src/cli.js +46 -52
  8. package/packages/agent-client/src/client.js +140 -5
  9. package/packages/agent-client/src/command-registry.js +56 -53
  10. package/packages/agent-client/src/content-extract.js +198 -0
  11. package/packages/agent-client/src/remotes.js +1 -1
  12. package/packages/agent-client/src/runtime.js +768 -35
  13. package/packages/agent-client/src/types.ts +131 -6
  14. package/packages/mcp-server/src/guidance.js +1 -1
  15. package/packages/mcp-server/src/handlers-capture.js +41 -5
  16. package/packages/mcp-server/src/handlers-dom.js +8 -2
  17. package/packages/mcp-server/src/handlers-navigation.js +2 -1
  18. package/packages/mcp-server/src/handlers-page.js +41 -4
  19. package/packages/mcp-server/src/handlers-utils.js +67 -7
  20. package/packages/mcp-server/src/handlers.js +4 -4
  21. package/packages/mcp-server/src/server.js +102 -6
  22. package/packages/native-host/src/daemon.js +158 -6
  23. package/packages/native-host/src/native-host.js +3 -1
  24. package/packages/protocol/src/defaults.js +3 -0
  25. package/packages/protocol/src/errors.js +54 -1
  26. package/packages/protocol/src/protocol.js +297 -1
  27. package/packages/protocol/src/registry.js +66 -35
  28. package/packages/protocol/src/summary.js +87 -5
  29. package/packages/protocol/src/types.ts +234 -0
  30. package/skills/browser-bridge/SKILL.md +34 -13
  31. package/skills/browser-bridge/references/interaction.md +55 -11
  32. package/skills/browser-bridge/references/patch-workflow.md +2 -0
  33. package/skills/browser-bridge/references/protocol.md +157 -82
  34. package/skills/browser-bridge/references/token-efficiency.md +19 -13
@@ -8,6 +8,7 @@ import {
8
8
  DEFAULT_CONSOLE_LIMIT,
9
9
  DEFAULT_DEVICE_SCALE_FACTOR,
10
10
  DEFAULT_EVAL_TIMEOUT_MS,
11
+ DEFAULT_EXTRACT_SETTLE_TIMEOUT_MS,
11
12
  DEFAULT_LOG_TAIL_LIMIT,
12
13
  DEFAULT_MAX_DEPTH,
13
14
  DEFAULT_MAX_HTML_LENGTH,
@@ -20,11 +21,13 @@ import {
20
21
  DEFAULT_VIEWPORT_HEIGHT,
21
22
  DEFAULT_VIEWPORT_WIDTH,
22
23
  DEFAULT_WAIT_TIMEOUT_MS,
24
+ MAX_EXTRACT_SETTLE_TIMEOUT_MS,
23
25
  } from './defaults.js';
24
26
  import { BridgeError, ERROR_CODES, getErrorRecovery } from './errors.js';
25
27
  import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry.js';
26
28
 
27
29
  /** @typedef {import('./types.js').AccessibilityTreeParams} AccessibilityTreeParams */
30
+ /** @typedef {import('./types.js').AccessRequestParams} AccessRequestParams */
28
31
  /** @typedef {import('./types.js').BridgeFailureResponse} BridgeFailureResponse */
29
32
  /** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
30
33
  /** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
@@ -37,16 +40,19 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
37
40
  /** @typedef {import('./types.js').DomQueryParams} DomQueryParams */
38
41
  /** @typedef {import('./types.js').DragParams} DragParams */
39
42
  /** @typedef {import('./types.js').EvaluateParams} EvaluateParams */
43
+ /** @typedef {import('./types.js').ExtractContentParams} ExtractContentParams */
40
44
  /** @typedef {import('./types.js').FindByRoleParams} FindByRoleParams */
41
45
  /** @typedef {import('./types.js').FindByTextParams} FindByTextParams */
42
46
  /** @typedef {import('./types.js').GetHtmlParams} GetHtmlParams */
43
47
  /** @typedef {import('./types.js').HoverParams} HoverParams */
48
+ /** @typedef {import('./types.js').HandleDialogParams} HandleDialogParams */
44
49
  /** @typedef {import('./types.js').InputActionParams} InputActionParams */
45
50
  /** @typedef {import('./types.js').LogTailParams} LogTailParams */
46
51
  /** @typedef {import('./types.js').NavigationActionParams} NavigationActionParams */
47
52
  /** @typedef {import('./types.js').NetworkInterceptAddParams} NetworkInterceptAddParams */
48
53
  /** @typedef {import('./types.js').NetworkParams} NetworkParams */
49
54
  /** @typedef {import('./types.js').NormalizedAccessibilityTreeParams} NormalizedAccessibilityTreeParams */
55
+ /** @typedef {import('./types.js').NormalizedAccessRequestParams} NormalizedAccessRequestParams */
50
56
  /** @typedef {import('./types.js').NormalizedCheckedAction} NormalizedCheckedAction */
51
57
  /** @typedef {import('./types.js').NormalizedCdpDispatchKeyEventParams} NormalizedCdpDispatchKeyEventParams */
52
58
  /** @typedef {import('./types.js').NormalizedCdpNodeIdParams} NormalizedCdpNodeIdParams */
@@ -54,10 +60,12 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
54
60
  /** @typedef {import('./types.js').NormalizedDomQuery} NormalizedDomQuery */
55
61
  /** @typedef {import('./types.js').NormalizedDragParams} NormalizedDragParams */
56
62
  /** @typedef {import('./types.js').NormalizedEvaluateParams} NormalizedEvaluateParams */
63
+ /** @typedef {import('./types.js').NormalizedExtractContentParams} NormalizedExtractContentParams */
57
64
  /** @typedef {import('./types.js').NormalizedFindByRoleParams} NormalizedFindByRoleParams */
58
65
  /** @typedef {import('./types.js').NormalizedFindByTextParams} NormalizedFindByTextParams */
59
66
  /** @typedef {import('./types.js').NormalizedGetHtmlParams} NormalizedGetHtmlParams */
60
67
  /** @typedef {import('./types.js').NormalizedHoverParams} NormalizedHoverParams */
68
+ /** @typedef {import('./types.js').NormalizedHandleDialogParams} NormalizedHandleDialogParams */
61
69
  /** @typedef {import('./types.js').NormalizedInputAction} NormalizedInputAction */
62
70
  /** @typedef {import('./types.js').NormalizedLogTailParams} NormalizedLogTailParams */
63
71
  /** @typedef {import('./types.js').NormalizedNavigationAction} NormalizedNavigationAction */
@@ -66,6 +74,7 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
66
74
  /** @typedef {import('./types.js').NormalizedPageTextParams} NormalizedPageTextParams */
67
75
  /** @typedef {import('./types.js').NormalizedPatchOperation} NormalizedPatchOperation */
68
76
  /** @typedef {import('./types.js').NormalizedSelectAction} NormalizedSelectAction */
77
+ /** @typedef {import('./types.js').NormalizedScreenshotParams} NormalizedScreenshotParams */
69
78
  /** @typedef {import('./types.js').NormalizedStorageParams} NormalizedStorageParams */
70
79
  /** @typedef {import('./types.js').NormalizedStyleQuery} NormalizedStyleQuery */
71
80
  /** @typedef {import('./types.js').NormalizedTabCloseParams} NormalizedTabCloseParams */
@@ -77,6 +86,7 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
77
86
  /** @typedef {import('./types.js').PageTextParams} PageTextParams */
78
87
  /** @typedef {import('./types.js').PatchOperationParams} PatchOperationParams */
79
88
  /** @typedef {import('./types.js').SelectActionParams} SelectActionParams */
89
+ /** @typedef {import('./types.js').ScreenshotParams} ScreenshotParams */
80
90
  /** @typedef {import('./types.js').StorageParams} StorageParams */
81
91
  /** @typedef {import('./types.js').StyleQueryParams} StyleQueryParams */
82
92
  /** @typedef {import('./types.js').TabCloseParams} TabCloseParams */
@@ -348,6 +358,8 @@ export function validateBridgeRequest(request) {
348
358
  */
349
359
  function normalizeRequestParams(method, params) {
350
360
  switch (method) {
361
+ case 'access.request':
362
+ return normalizeAccessRequestParams(params);
351
363
  case 'log.tail':
352
364
  return normalizeLogTailParams(params);
353
365
  case 'dom.query':
@@ -356,12 +368,16 @@ function normalizeRequestParams(method, params) {
356
368
  return normalizeEvaluateParams(params);
357
369
  case 'page.get_console':
358
370
  return normalizeConsoleParams(params);
371
+ case 'page.handle_dialog':
372
+ return normalizeHandleDialogParams(params);
359
373
  case 'page.wait_for_load_state':
360
374
  return normalizeWaitForLoadStateParams(params);
361
375
  case 'page.get_storage':
362
376
  return normalizeStorageParams(params);
363
377
  case 'page.get_text':
364
378
  return normalizePageTextParams(params);
379
+ case 'page.extract_content':
380
+ return normalizeExtractContentParams(params);
365
381
  case 'page.get_network':
366
382
  return normalizeNetworkParams(params);
367
383
  case 'network.intercept.add':
@@ -407,6 +423,10 @@ function normalizeRequestParams(method, params) {
407
423
  return normalizeHoverParams(params);
408
424
  case 'input.drag':
409
425
  return normalizeDragParams(params);
426
+ case 'screenshot.capture_region':
427
+ case 'screenshot.capture_element':
428
+ case 'screenshot.capture_full_page':
429
+ return { ...params, ...normalizeScreenshotParams(params) };
410
430
  case 'patch.apply_styles':
411
431
  case 'patch.apply_dom':
412
432
  case 'patch.list':
@@ -422,6 +442,28 @@ function normalizeRequestParams(method, params) {
422
442
  }
423
443
  }
424
444
 
445
+ /**
446
+ * @param {AccessRequestParams} [params={}]
447
+ * @returns {NormalizedAccessRequestParams}
448
+ */
449
+ export function normalizeAccessRequestParams(params = {}) {
450
+ const intent = params.intent ?? 'general';
451
+ if (
452
+ intent !== 'inspect' &&
453
+ intent !== 'interact' &&
454
+ intent !== 'capture' &&
455
+ intent !== 'navigate' &&
456
+ intent !== 'debugger' &&
457
+ intent !== 'general'
458
+ ) {
459
+ throw new BridgeError(
460
+ ERROR_CODES.INVALID_REQUEST,
461
+ 'intent must be one of inspect, interact, capture, navigate, debugger, or general.'
462
+ );
463
+ }
464
+ return { intent };
465
+ }
466
+
425
467
  /**
426
468
  * Return the browser-operation timeout for methods that wait asynchronously.
427
469
  * This lets clients and the daemon keep transport deadlines outside the
@@ -442,6 +484,10 @@ export function getBridgeOperationTimeoutMs(method, params = {}) {
442
484
  return normalizeWaitForLoadStateParams(params).timeoutMs;
443
485
  case 'page.evaluate':
444
486
  return normalizeEvaluateParams(params).timeoutMs;
487
+ case 'page.extract_content': {
488
+ const normalized = normalizeExtractContentParams(params);
489
+ return normalized.consistency === 'settled' ? normalized.settleTimeoutMs : null;
490
+ }
445
491
  case 'dom.wait_for':
446
492
  return clampInt(params.timeoutMs, 100, 30_000, 5_000);
447
493
  default:
@@ -499,6 +545,17 @@ function normalizeTarget(target) {
499
545
  };
500
546
  }
501
547
 
548
+ /**
549
+ * @param {unknown} value
550
+ * @returns {import('./types.js').InputExecutionMode}
551
+ */
552
+ function normalizeInputExecutionMode(value) {
553
+ if (value !== undefined && value !== 'dom' && value !== 'cdp') {
554
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'executionMode must be either dom or cdp.');
555
+ }
556
+ return value === 'cdp' ? 'cdp' : 'dom';
557
+ }
558
+
502
559
  /**
503
560
  * @param {InputActionParams} [params={}]
504
561
  * @returns {NormalizedInputAction}
@@ -527,6 +584,8 @@ export function normalizeInputAction(params = {}) {
527
584
  modifiers: Array.isArray(params.modifiers)
528
585
  ? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
529
586
  : [],
587
+ executionMode: normalizeInputExecutionMode(params.executionMode),
588
+ recoverStale: params.recoverStale === true,
530
589
  };
531
590
  }
532
591
 
@@ -604,6 +663,8 @@ export function normalizeCheckedAction(params = {}) {
604
663
  : undefined
605
664
  ),
606
665
  checked: params.checked !== false,
666
+ executionMode: normalizeInputExecutionMode(params.executionMode),
667
+ recoverStale: params.recoverStale === true,
607
668
  };
608
669
  }
609
670
 
@@ -629,6 +690,8 @@ export function normalizeSelectAction(params = {}) {
629
690
  .map((index) => Number(index))
630
691
  .filter((index) => Number.isInteger(index) && index >= 0)
631
692
  : [],
693
+ executionMode: normalizeInputExecutionMode(params.executionMode),
694
+ recoverStale: params.recoverStale === true,
632
695
  };
633
696
  }
634
697
 
@@ -739,6 +802,62 @@ export function normalizeConsoleParams(params = {}) {
739
802
  };
740
803
  }
741
804
 
805
+ /**
806
+ * @param {HandleDialogParams} [params={}]
807
+ * @returns {NormalizedHandleDialogParams}
808
+ */
809
+ export function normalizeHandleDialogParams(params = {}) {
810
+ const action = params.action ?? 'inspect';
811
+ if (action !== 'inspect' && action !== 'accept' && action !== 'dismiss') {
812
+ throw new BridgeError(
813
+ ERROR_CODES.INVALID_REQUEST,
814
+ 'action must be one of inspect, accept, or dismiss.'
815
+ );
816
+ }
817
+ if (
818
+ params.promptText !== undefined &&
819
+ params.promptText !== null &&
820
+ typeof params.promptText !== 'string'
821
+ ) {
822
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'promptText must be a string.');
823
+ }
824
+ if (typeof params.promptText === 'string' && params.promptText.length > 10_000) {
825
+ throw new BridgeError(
826
+ ERROR_CODES.INVALID_REQUEST,
827
+ 'promptText must not exceed 10,000 characters.'
828
+ );
829
+ }
830
+ if (params.promptText !== undefined && params.promptText !== null && action !== 'accept') {
831
+ throw new BridgeError(
832
+ ERROR_CODES.INVALID_REQUEST,
833
+ 'promptText is only valid when action is accept.'
834
+ );
835
+ }
836
+ if (
837
+ params.expectedDialogId !== undefined &&
838
+ params.expectedDialogId !== null &&
839
+ (typeof params.expectedDialogId !== 'string' ||
840
+ params.expectedDialogId.length === 0 ||
841
+ params.expectedDialogId.length > 128)
842
+ ) {
843
+ throw new BridgeError(
844
+ ERROR_CODES.INVALID_REQUEST,
845
+ 'expectedDialogId must be a non-empty string of at most 128 characters.'
846
+ );
847
+ }
848
+ if (action === 'inspect' && params.expectedDialogId != null) {
849
+ throw new BridgeError(
850
+ ERROR_CODES.INVALID_REQUEST,
851
+ 'expectedDialogId is only valid when action is accept or dismiss.'
852
+ );
853
+ }
854
+ return {
855
+ action,
856
+ promptText: typeof params.promptText === 'string' ? params.promptText : null,
857
+ expectedDialogId: typeof params.expectedDialogId === 'string' ? params.expectedDialogId : null,
858
+ };
859
+ }
860
+
742
861
  /**
743
862
  * @param {WaitForParams} [params={}]
744
863
  * @returns {NormalizedWaitForParams}
@@ -823,6 +942,8 @@ export function normalizeHoverParams(params = {}) {
823
942
  modifiers: Array.isArray(params.modifiers)
824
943
  ? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
825
944
  : [],
945
+ executionMode: normalizeInputExecutionMode(params.executionMode),
946
+ recoverStale: params.recoverStale === true,
826
947
  };
827
948
  }
828
949
 
@@ -844,6 +965,8 @@ export function normalizeDragParams(params = {}) {
844
965
  ),
845
966
  offsetX: Number.isFinite(Number(params.offsetX)) ? Number(params.offsetX) : 0,
846
967
  offsetY: Number.isFinite(Number(params.offsetY)) ? Number(params.offsetY) : 0,
968
+ executionMode: normalizeInputExecutionMode(params.executionMode),
969
+ recoverStale: params.recoverStale === true,
847
970
  };
848
971
  }
849
972
 
@@ -863,12 +986,98 @@ export function normalizeStorageParams(params = {}) {
863
986
  * @returns {NormalizedWaitForLoadStateParams}
864
987
  */
865
988
  export function normalizeWaitForLoadStateParams(params = {}) {
989
+ const url = typeof params.url === 'string' && params.url.length > 0 ? params.url : null;
990
+ const requestedMatch = params.urlMatch ?? (url ? 'exact' : null);
991
+ if (params.url !== undefined && params.url !== null && typeof params.url !== 'string') {
992
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'url must be a string.');
993
+ }
994
+ if (params.urlMatch !== undefined && params.urlMatch !== null && !url) {
995
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'url is required when urlMatch is set.');
996
+ }
997
+ if (
998
+ requestedMatch !== null &&
999
+ requestedMatch !== 'exact' &&
1000
+ requestedMatch !== 'contains' &&
1001
+ requestedMatch !== 'regex'
1002
+ ) {
1003
+ throw new BridgeError(
1004
+ ERROR_CODES.INVALID_REQUEST,
1005
+ 'urlMatch must be one of exact, contains, or regex.'
1006
+ );
1007
+ }
1008
+ if (url && url.length > 2_048) {
1009
+ throw new BridgeError(
1010
+ ERROR_CODES.INVALID_REQUEST,
1011
+ 'url condition must not exceed 2,048 characters.'
1012
+ );
1013
+ }
1014
+ if (requestedMatch === 'regex' && url) {
1015
+ validateSafeUrlRegex(url);
1016
+ }
866
1017
  return {
867
1018
  waitForLoad: params.waitForLoad !== false,
868
1019
  timeoutMs: clampInt(params.timeoutMs, 500, 120_000, DEFAULT_NAV_TIMEOUT_MS),
1020
+ url,
1021
+ urlMatch: requestedMatch,
869
1022
  };
870
1023
  }
871
1024
 
1025
+ /**
1026
+ * URL waits accept a deliberately linear regular-expression subset. Patterns
1027
+ * may contain literals, escapes, anchors, dots, and character classes, but no
1028
+ * grouping, alternation, or quantifiers. With both pattern and input bounded,
1029
+ * the accepted subset cannot trigger backtracking through repeated matches.
1030
+ *
1031
+ * @param {string} pattern
1032
+ * @returns {void}
1033
+ */
1034
+ function validateSafeUrlRegex(pattern) {
1035
+ if (pattern.length > 256) {
1036
+ throw new BridgeError(
1037
+ ERROR_CODES.INVALID_REQUEST,
1038
+ 'Regex URL condition must not exceed 256 characters.'
1039
+ );
1040
+ }
1041
+ let escaped = false;
1042
+ let inCharacterClass = false;
1043
+ for (let index = 0; index < pattern.length; index += 1) {
1044
+ const character = pattern[index];
1045
+ if (escaped) {
1046
+ if (!inCharacterClass && /[1-9]/.test(character)) {
1047
+ throw new BridgeError(
1048
+ ERROR_CODES.INVALID_REQUEST,
1049
+ 'Regex URL condition uses an unsupported backreference.'
1050
+ );
1051
+ }
1052
+ escaped = false;
1053
+ continue;
1054
+ }
1055
+ if (character === '\\') {
1056
+ escaped = true;
1057
+ continue;
1058
+ }
1059
+ if (character === '[' && !inCharacterClass) {
1060
+ inCharacterClass = true;
1061
+ continue;
1062
+ }
1063
+ if (character === ']' && inCharacterClass) {
1064
+ inCharacterClass = false;
1065
+ continue;
1066
+ }
1067
+ if (!inCharacterClass && '(){}*+?|'.includes(character)) {
1068
+ throw new BridgeError(
1069
+ ERROR_CODES.INVALID_REQUEST,
1070
+ 'Regex URL condition uses unsupported grouping, alternation, or quantifiers.'
1071
+ );
1072
+ }
1073
+ }
1074
+ try {
1075
+ new RegExp(pattern);
1076
+ } catch {
1077
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'Regex URL condition is invalid.');
1078
+ }
1079
+ }
1080
+
872
1081
  /**
873
1082
  * @param {TabCreateParams} [params={}]
874
1083
  * @returns {NormalizedTabCreateParams}
@@ -914,8 +1123,33 @@ export function normalizeTabCloseParams(params = {}) {
914
1123
  */
915
1124
  export function normalizeAccessibilityTreeParams(params = {}) {
916
1125
  return {
1126
+ selector:
1127
+ typeof params.selector === 'string' && params.selector.trim() ? params.selector.trim() : null,
917
1128
  maxDepth: clampInt(params.maxDepth, 1, 20, DEFAULT_A11Y_MAX_DEPTH),
918
1129
  maxNodes: clampInt(params.maxNodes, 10, 5000, DEFAULT_A11Y_MAX_NODES),
1130
+ compact: params.compact === true,
1131
+ interactiveOnly: params.interactiveOnly === true,
1132
+ };
1133
+ }
1134
+
1135
+ /**
1136
+ * @param {ScreenshotParams} [params={}]
1137
+ * @returns {NormalizedScreenshotParams}
1138
+ */
1139
+ export function normalizeScreenshotParams(params = {}) {
1140
+ const format = params.format ?? 'png';
1141
+ if (format !== 'png' && format !== 'jpeg' && format !== 'webp') {
1142
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'format must be png, jpeg, or webp.');
1143
+ }
1144
+ const numericQuality = Number(params.quality);
1145
+ return {
1146
+ format,
1147
+ quality:
1148
+ format === 'png' || params.quality === undefined
1149
+ ? null
1150
+ : Number.isFinite(numericQuality)
1151
+ ? Math.min(100, Math.max(0, Math.trunc(numericQuality)))
1152
+ : 80,
919
1153
  };
920
1154
  }
921
1155
 
@@ -924,6 +1158,20 @@ export function normalizeAccessibilityTreeParams(params = {}) {
924
1158
  * @returns {NormalizedNetworkParams}
925
1159
  */
926
1160
  export function normalizeNetworkParams(params = {}) {
1161
+ const source = params.source ?? 'fetch-xhr';
1162
+ if (source !== 'fetch-xhr' && source !== 'cdp') {
1163
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'source must be fetch-xhr or cdp.');
1164
+ }
1165
+ const capture = params.capture ?? 'read';
1166
+ if (capture !== 'read' && capture !== 'start' && capture !== 'clear' && capture !== 'stop') {
1167
+ throw new BridgeError(
1168
+ ERROR_CODES.INVALID_REQUEST,
1169
+ 'capture must be read, start, clear, or stop.'
1170
+ );
1171
+ }
1172
+ if (source !== 'cdp' && params.capture !== undefined && params.capture !== 'read') {
1173
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'capture is only valid with source "cdp".');
1174
+ }
927
1175
  return {
928
1176
  clear: Boolean(params.clear),
929
1177
  limit: clampInt(params.limit, 1, 500, DEFAULT_NETWORK_LIMIT),
@@ -931,6 +1179,8 @@ export function normalizeNetworkParams(params = {}) {
931
1179
  typeof params.urlPattern === 'string' && params.urlPattern.trim()
932
1180
  ? params.urlPattern.trim()
933
1181
  : null,
1182
+ source,
1183
+ capture,
934
1184
  };
935
1185
  }
936
1186
 
@@ -1009,6 +1259,44 @@ export function normalizePageTextParams(params = {}) {
1009
1259
  };
1010
1260
  }
1011
1261
 
1262
+ /**
1263
+ * @param {ExtractContentParams} [params={}]
1264
+ * @returns {NormalizedExtractContentParams}
1265
+ */
1266
+ export function normalizeExtractContentParams(params = {}) {
1267
+ const format = params.format ?? 'text';
1268
+ if (format !== 'text' && format !== 'markdown') {
1269
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'format must be text or markdown.');
1270
+ }
1271
+ const consistency = params.consistency ?? 'best_effort';
1272
+ if (consistency !== 'best_effort' && consistency !== 'settled') {
1273
+ throw new BridgeError(
1274
+ ERROR_CODES.INVALID_REQUEST,
1275
+ 'consistency must be best_effort or settled.'
1276
+ );
1277
+ }
1278
+ const selector = typeof params.selector === 'string' ? params.selector.trim() : '';
1279
+ if (selector.length > 2048) {
1280
+ throw new BridgeError(
1281
+ ERROR_CODES.INVALID_REQUEST,
1282
+ 'selector must not exceed 2,048 characters.'
1283
+ );
1284
+ }
1285
+ return {
1286
+ format,
1287
+ selector: selector || null,
1288
+ includeMetadata: params.includeMetadata !== false,
1289
+ consistency,
1290
+ textBudget: clampInt(params.textBudget, 100, 100_000, DEFAULT_PAGE_TEXT_BUDGET),
1291
+ settleTimeoutMs: clampInt(
1292
+ params.settleTimeoutMs,
1293
+ 100,
1294
+ MAX_EXTRACT_SETTLE_TIMEOUT_MS,
1295
+ DEFAULT_EXTRACT_SETTLE_TIMEOUT_MS
1296
+ ),
1297
+ };
1298
+ }
1299
+
1012
1300
  /**
1013
1301
  * @param {LogTailParams} [params={}]
1014
1302
  * @returns {NormalizedLogTailParams}
@@ -1071,6 +1359,14 @@ export function createRuntimeContext() {
1071
1359
  'Browser Bridge is off for this window or the page is restricted; if access is off, the first denied call surfaces an Enable cue in the extension UI',
1072
1360
  TAB_MISMATCH: 'Tab closed or not found',
1073
1361
  ELEMENT_STALE: 'Element removed from DOM - re-query',
1362
+ ELEMENT_AMBIGUOUS: 'Input selector matched equally actionable elements - narrow selector',
1363
+ ELEMENT_NOT_ACTIONABLE: 'Input target is hidden, disabled, inert, or not rendered',
1364
+ ELEMENT_OBSCURED: 'Pointer target is blocked by an unrelated element',
1365
+ ELEMENT_NOT_FOUND: 'Input selector did not match an element',
1366
+ INPUT_UNSUPPORTED: 'Execution mode does not support this input operation',
1367
+ INPUT_INVALID_TARGET: 'Input target is incompatible with the requested operation',
1368
+ INPUT_FOCUS_CHANGED: 'Focus moved before native text dispatch',
1369
+ DIALOG_NOT_OPEN: 'No JavaScript dialog is currently observable',
1074
1370
  INVALID_REQUEST: 'Malformed method or params',
1075
1371
  TIMEOUT: 'Operation exceeded time limit',
1076
1372
  RATE_LIMITED: 'Too many requests - back off',
@@ -1091,7 +1387,7 @@ export function createRuntimeContext() {
1091
1387
  'If a tab-bound call returns ACCESS_DENIED because access is off, ask the user to click Enable in the Browser Bridge popup or side panel, then retry once',
1092
1388
  'dom.wait_for after HMR / navigation to detect page updates',
1093
1389
  'page.get_console to catch runtime errors after interactions',
1094
- 'page.get_network to inspect XHR/fetch API calls',
1390
+ 'page.get_network for fetch/XHR reads or explicit all-resource CDP capture',
1095
1391
  'page.get_text for full-page content extraction',
1096
1392
  'input.hover before screenshot to inspect hover states',
1097
1393
  'performance.get_metrics for Core Web Vitals and load timing',