@browserbridge/bbx 1.8.0 → 1.9.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 (39) hide show
  1. package/PRIVACY.md +29 -3
  2. package/README.md +10 -3
  3. package/SECURITY.md +12 -0
  4. package/package.json +7 -1
  5. package/packages/agent-client/src/cli-args.js +35 -0
  6. package/packages/agent-client/src/cli-batch.js +121 -58
  7. package/packages/agent-client/src/cli-output.js +8 -1
  8. package/packages/agent-client/src/cli.js +227 -19
  9. package/packages/agent-client/src/client.js +156 -3
  10. package/packages/agent-client/src/command-registry.js +8 -2
  11. package/packages/agent-client/src/config.js +195 -0
  12. package/packages/agent-client/src/npm-self-update.js +305 -0
  13. package/packages/agent-client/src/remotes.js +4 -2
  14. package/packages/agent-client/src/runtime.js +97 -5
  15. package/packages/agent-client/src/types.ts +38 -0
  16. package/packages/mcp-server/src/handlers-capture.js +29 -2
  17. package/packages/mcp-server/src/handlers-dom.js +54 -3
  18. package/packages/mcp-server/src/handlers-page.js +180 -60
  19. package/packages/mcp-server/src/handlers-utils.js +35 -4
  20. package/packages/mcp-server/src/handlers.js +10 -3
  21. package/packages/mcp-server/src/server.js +75 -6
  22. package/packages/native-host/src/artifact-store.js +353 -0
  23. package/packages/native-host/src/config.js +9 -1
  24. package/packages/native-host/src/daemon-logger.js +3 -1
  25. package/packages/native-host/src/daemon.js +543 -27
  26. package/packages/native-host/src/native-host.js +25 -2
  27. package/packages/protocol/src/capability-values.js +2 -0
  28. package/packages/protocol/src/defaults.js +28 -0
  29. package/packages/protocol/src/errors.js +48 -5
  30. package/packages/protocol/src/incidental-sanitizer.js +197 -0
  31. package/packages/protocol/src/index.js +2 -0
  32. package/packages/protocol/src/protocol.js +316 -6
  33. package/packages/protocol/src/recovery-telemetry.js +302 -0
  34. package/packages/protocol/src/registry.js +143 -6
  35. package/packages/protocol/src/summary.js +116 -25
  36. package/packages/protocol/src/types.ts +489 -4
  37. package/skills/browser-bridge/SKILL.md +22 -8
  38. package/skills/browser-bridge/references/protocol.md +117 -13
  39. package/skills/browser-bridge/references/token-efficiency.md +17 -7
@@ -29,7 +29,8 @@ export type Capability =
29
29
  | 'tabs.manage'
30
30
  | 'performance.read'
31
31
  | 'network.read'
32
- | 'network.intercept';
32
+ | 'network.intercept'
33
+ | 'sensitive.read';
33
34
 
34
35
  export type ErrorCode =
35
36
  | 'ACCESS_DENIED'
@@ -45,7 +46,15 @@ export type ErrorCode =
45
46
  | 'DIALOG_NOT_OPEN'
46
47
  | 'DIALOG_ACTION_CONFLICT'
47
48
  | 'RESULT_TRUNCATED'
48
- | 'RATE_LIMITED'
49
+ | 'RESULT_TOO_LARGE'
50
+ | 'SENSITIVE_TARGET_NOT_FOUND'
51
+ | 'CONTENT_SCRIPT_UNAVAILABLE'
52
+ | 'ARTIFACT_NOT_FOUND'
53
+ | 'ARTIFACT_QUOTA_EXCEEDED'
54
+ | 'ARTIFACT_TRANSFER_INVALID'
55
+ | 'DOM_BASELINE_NOT_FOUND'
56
+ | 'DOM_BASELINE_INVALIDATED'
57
+ | 'DOM_BASELINE_QUOTA_EXCEEDED'
49
58
  | 'INTERNAL_ERROR'
50
59
  | 'INVALID_REQUEST'
51
60
  | 'NATIVE_HOST_UNAVAILABLE'
@@ -67,9 +76,11 @@ export type BridgeMethod =
67
76
  | 'page.handle_dialog'
68
77
  | 'page.wait_for_load_state'
69
78
  | 'page.get_storage'
79
+ | 'sensitive.read'
70
80
  | 'page.get_text'
71
81
  | 'page.extract_content'
72
82
  | 'page.get_network'
83
+ | 'network.export_har'
73
84
  | 'network.intercept.add'
74
85
  | 'network.intercept.remove'
75
86
  | 'network.intercept.list'
@@ -79,6 +90,10 @@ export type BridgeMethod =
79
90
  | 'navigation.go_back'
80
91
  | 'navigation.go_forward'
81
92
  | 'dom.query'
93
+ | 'dom.baseline.create'
94
+ | 'dom.baseline.compare'
95
+ | 'dom.baseline.describe'
96
+ | 'dom.baseline.release'
82
97
  | 'dom.describe'
83
98
  | 'dom.get_text'
84
99
  | 'dom.get_attributes'
@@ -106,6 +121,8 @@ export type BridgeMethod =
106
121
  | 'screenshot.capture_region'
107
122
  | 'screenshot.capture_element'
108
123
  | 'screenshot.capture_full_page'
124
+ | 'artifact.read'
125
+ | 'artifact.delete'
109
126
  | 'patch.apply_styles'
110
127
  | 'patch.apply_dom'
111
128
  | 'patch.list'
@@ -142,9 +159,44 @@ export interface BridgeMeta {
142
159
  budget_applied?: boolean;
143
160
  budget_truncated?: boolean;
144
161
  continuation_hint?: string | null;
162
+ automatic_retry?: { attempt: 2; reason: 'retryable_error' };
163
+ stale_recovery?: 'success' | 'failure';
145
164
  [key: string]: unknown;
146
165
  }
147
166
 
167
+ export type RecoveryScope = 'routedExtension' | 'daemon';
168
+
169
+ export type RecoveryEventKind =
170
+ | 'automatic_mcp_retry'
171
+ | 'stale_ref_recovery'
172
+ | 'debugger_reattach'
173
+ | 'content_script_reinjection'
174
+ | 'native_host_reconnect'
175
+ | 'request_outcome';
176
+
177
+ export interface RecoveryEventSummary {
178
+ attempts: number;
179
+ successes: number;
180
+ failures: number;
181
+ pending: number;
182
+ saturated: boolean;
183
+ successRate: number | null;
184
+ failureRate: number | null;
185
+ activeLoop: boolean;
186
+ lastEventAt: number | null;
187
+ }
188
+
189
+ export interface RecoveryTelemetrySummary {
190
+ scope: RecoveryScope;
191
+ windowMs: number;
192
+ bucketMs: number;
193
+ loopWindowMs: number;
194
+ loopThreshold: number;
195
+ asOf: number;
196
+ activeLoop: boolean;
197
+ events: Record<RecoveryEventKind, RecoveryEventSummary>;
198
+ }
199
+
148
200
  export interface BridgeParams {
149
201
  [key: string]: unknown;
150
202
  }
@@ -223,6 +275,147 @@ export interface NormalizedDomQuery extends BridgeParams {
223
275
  budget: Budget;
224
276
  }
225
277
 
278
+ export interface DomBaselineCreateParams {
279
+ selector?: string;
280
+ maxNodes?: number;
281
+ maxDepth?: number;
282
+ textBudget?: number;
283
+ attributeAllowlist?: string[];
284
+ }
285
+
286
+ export interface NormalizedDomBaselineCreateParams extends BridgeParams {
287
+ selector: string;
288
+ maxNodes: number;
289
+ maxDepth: number;
290
+ textBudget: number;
291
+ attributeAllowlist: string[];
292
+ }
293
+
294
+ export interface DomBaselineCompareParams {
295
+ baselineId?: string;
296
+ maxChanges?: number;
297
+ }
298
+
299
+ export interface NormalizedDomBaselineCompareParams extends BridgeParams {
300
+ baselineId: string;
301
+ maxChanges: number;
302
+ }
303
+
304
+ export interface DomBaselineHandleParams {
305
+ baselineId?: string;
306
+ }
307
+
308
+ export interface NormalizedDomBaselineHandleParams extends BridgeParams {
309
+ baselineId: string;
310
+ }
311
+
312
+ export interface DomBaselineOptions {
313
+ maxNodes: number;
314
+ maxDepth: number;
315
+ textBudget: number;
316
+ attributeAllowlist: string[];
317
+ }
318
+
319
+ export interface DomBaselineScope {
320
+ windowId: number;
321
+ tabId: number;
322
+ frameId: number;
323
+ documentToken: string;
324
+ selector: string;
325
+ representation: 'semantic-dom-v1';
326
+ }
327
+
328
+ export interface DomBaselineSnapshotStats {
329
+ nodeCount: number;
330
+ byteLength: number;
331
+ digest: string;
332
+ }
333
+
334
+ export interface DomBaselineDescriptor {
335
+ baselineId: string;
336
+ createdAt: string;
337
+ expiresAt: string;
338
+ scope: DomBaselineScope;
339
+ options: DomBaselineOptions;
340
+ snapshot: DomBaselineSnapshotStats;
341
+ evicted?: Array<{
342
+ baselineId: string;
343
+ reason: 'per_tab_quota' | 'global_quota';
344
+ }>;
345
+ }
346
+
347
+ export type DomBaselineCreateResult = DomBaselineDescriptor;
348
+ export type DomBaselineDescribeResult = DomBaselineDescriptor;
349
+
350
+ export interface DomBaselineSemanticNodeEvidence {
351
+ tag: string;
352
+ role: string | null;
353
+ name: string | null;
354
+ text: string;
355
+ attributes: Record<string, string>;
356
+ depth: number;
357
+ }
358
+
359
+ export type DomBaselineNodeEvidence = DomBaselineSemanticNodeEvidence;
360
+
361
+ export interface DomBaselineChangedNode {
362
+ fields: string[];
363
+ before: DomBaselineSemanticNodeEvidence;
364
+ after: DomBaselineSemanticNodeEvidence;
365
+ }
366
+
367
+ export interface DomBaselineNodeLocation {
368
+ ancestry: string[];
369
+ order: number;
370
+ }
371
+
372
+ export interface DomBaselineMovedNode {
373
+ node: DomBaselineSemanticNodeEvidence;
374
+ from: DomBaselineNodeLocation;
375
+ to: DomBaselineNodeLocation;
376
+ }
377
+
378
+ export interface DomBaselineChangeCounts {
379
+ added: number;
380
+ removed: number;
381
+ changed: number;
382
+ moved: number;
383
+ unchanged: number;
384
+ total: number;
385
+ }
386
+
387
+ export interface DomBaselineReturnedCounts {
388
+ added: number;
389
+ removed: number;
390
+ changed: number;
391
+ moved: number;
392
+ total: number;
393
+ }
394
+
395
+ export interface DomBaselineCompareResult {
396
+ baselineId: string;
397
+ equal: boolean;
398
+ comparedAt: string;
399
+ counts: DomBaselineChangeCounts;
400
+ returnedCounts: DomBaselineReturnedCounts;
401
+ added: DomBaselineSemanticNodeEvidence[];
402
+ removed: DomBaselineSemanticNodeEvidence[];
403
+ changed: DomBaselineChangedNode[];
404
+ moved: DomBaselineMovedNode[];
405
+ truncated: boolean;
406
+ omittedChanges: number;
407
+ ambiguity: {
408
+ count: number;
409
+ examples: DomBaselineSemanticNodeEvidence[];
410
+ };
411
+ guidance: string;
412
+ }
413
+
414
+ export interface DomBaselineReleaseResult {
415
+ baselineId: string;
416
+ released: boolean;
417
+ }
418
+
226
419
  export interface DomFindResult {
227
420
  found: boolean;
228
421
  nodes: Record<string, unknown>[];
@@ -312,6 +505,10 @@ export interface CdpNodeIdParams {
312
505
  nodeId?: number;
313
506
  }
314
507
 
508
+ export interface CdpDomSnapshotParams {
509
+ computedStyles?: string[];
510
+ }
511
+
315
512
  export interface NormalizedCdpDispatchKeyEventParams extends BridgeParams {
316
513
  key: string;
317
514
  code: string;
@@ -322,6 +519,10 @@ export interface NormalizedCdpNodeIdParams extends BridgeParams {
322
519
  nodeId: number;
323
520
  }
324
521
 
522
+ export interface NormalizedCdpDomSnapshotParams extends BridgeParams {
523
+ computedStyles: string[];
524
+ }
525
+
325
526
  export interface CheckedActionParams {
326
527
  target?: InputTarget;
327
528
  checked?: boolean;
@@ -594,10 +795,31 @@ export interface NormalizedStorageParams extends BridgeParams {
594
795
  keys: string[] | null;
595
796
  }
596
797
 
798
+ export type SensitiveStorageSource = 'local_storage' | 'session_storage';
799
+
800
+ export interface SensitiveReadParams {
801
+ source: SensitiveStorageSource;
802
+ key: string;
803
+ }
804
+
805
+ export interface NormalizedSensitiveReadParams extends BridgeParams {
806
+ source: SensitiveStorageSource;
807
+ key: string;
808
+ maxBytes: number;
809
+ }
810
+
811
+ export interface SensitiveReadResult {
812
+ source: SensitiveStorageSource;
813
+ value: string;
814
+ exact: true;
815
+ }
816
+
597
817
  export type ExtractContentFormat = 'text' | 'markdown';
598
818
  export type ExtractContentConsistency = 'best_effort' | 'settled';
599
819
  export type ExtractContentSource = 'readability' | 'semantic-root' | 'body';
600
820
  export type ScreenshotFormat = 'png' | 'jpeg' | 'webp';
821
+ export type ScreenshotDelivery = 'auto' | 'inline' | 'artifact';
822
+ export type ArtifactKind = 'screenshot' | 'har';
601
823
 
602
824
  export interface ExtractContentParams {
603
825
  format?: ExtractContentFormat;
@@ -620,22 +842,62 @@ export interface NormalizedExtractContentParams extends BridgeParams {
620
842
  export interface ScreenshotParams extends BridgeParams {
621
843
  format?: ScreenshotFormat;
622
844
  quality?: number;
845
+ delivery?: ScreenshotDelivery;
846
+ scale?: number;
623
847
  }
624
848
 
625
849
  export interface NormalizedScreenshotParams extends BridgeParams {
626
850
  format: ScreenshotFormat;
627
851
  quality: number | null;
852
+ delivery: ScreenshotDelivery;
853
+ scale: number;
628
854
  }
629
855
 
630
- export interface ScreenshotResult {
631
- image: string;
856
+ export interface ArtifactDescriptor<K extends ArtifactKind = ArtifactKind> {
857
+ artifactId: string;
858
+ kind: K;
859
+ mimeType: string;
860
+ byteLength: number;
861
+ sha256: string;
862
+ chunkSize: number;
863
+ chunkCount: number;
864
+ createdAt: string;
865
+ expiresAt: string;
866
+ }
867
+
868
+ export interface ScreenshotMetadata {
632
869
  mimeType: `image/${ScreenshotFormat}`;
633
870
  format: ScreenshotFormat;
871
+ byteLength: number;
872
+ dimensions: { width: number; height: number };
634
873
  rect: { x: number; y: number; width: number; height: number; scale: number };
635
874
  complete: boolean;
636
875
  clipped: boolean;
637
876
  }
638
877
 
878
+ export type ScreenshotResult =
879
+ | (ScreenshotMetadata & { delivery: 'inline'; image: string })
880
+ | (ScreenshotMetadata & {
881
+ delivery: 'artifact';
882
+ artifact: ArtifactDescriptor<'screenshot'>;
883
+ });
884
+
885
+ export interface ArtifactReadParams extends BridgeParams {
886
+ artifactId?: string;
887
+ offset?: number;
888
+ maxBytes?: number;
889
+ }
890
+
891
+ export interface NormalizedArtifactReadParams extends BridgeParams {
892
+ artifactId: string;
893
+ offset: number;
894
+ maxBytes: number;
895
+ }
896
+
897
+ export interface ArtifactDeleteParams extends BridgeParams {
898
+ artifactId?: string;
899
+ }
900
+
639
901
  export interface ExtractContentResult {
640
902
  format: ExtractContentFormat;
641
903
  content: string;
@@ -799,6 +1061,229 @@ export interface NetworkResult {
799
1061
  truncation: { reason: 'limit' | null; limit: number; omitted: number };
800
1062
  }
801
1063
 
1064
+ export interface CdpPerformanceMetric {
1065
+ name: string;
1066
+ value: number;
1067
+ }
1068
+
1069
+ export interface PerformanceMeasurement {
1070
+ source: 'cdp.Performance.getMetrics';
1071
+ kind: 'raw_cdp_counters';
1072
+ sampledAt: string;
1073
+ timeDomain: 'timeTicks';
1074
+ observation: 'browser_maintained_point_sample';
1075
+ webVitals: 'not_measured';
1076
+ }
1077
+
1078
+ export interface PerformanceMetricsResult {
1079
+ metrics: Record<string, number>;
1080
+ measurement: PerformanceMeasurement;
1081
+ }
1082
+
1083
+ export type HarDelivery = 'auto' | 'inline' | 'artifact';
1084
+ export type HarCaptureState = NetworkResult['captureState'];
1085
+
1086
+ export interface HarExportParams {
1087
+ limit?: number;
1088
+ urlPattern?: string;
1089
+ delivery?: HarDelivery;
1090
+ }
1091
+
1092
+ export interface NormalizedHarExportParams extends BridgeParams {
1093
+ limit: number;
1094
+ urlPattern: string | null;
1095
+ delivery: HarDelivery;
1096
+ }
1097
+
1098
+ export interface HarEvidenceEntry extends Record<string, unknown> {
1099
+ url: string;
1100
+ method: string;
1101
+ resourceType: string;
1102
+ status: number;
1103
+ mimeType: string;
1104
+ protocol: string;
1105
+ fromCache: boolean;
1106
+ fromDiskCache: boolean;
1107
+ fromServiceWorker: boolean;
1108
+ fromPrefetchCache: boolean;
1109
+ failureReason: string;
1110
+ redirectURL: string;
1111
+ duration: number;
1112
+ startedAt: number;
1113
+ }
1114
+
1115
+ export interface HarNameValue extends Record<string, unknown> {
1116
+ name: string;
1117
+ value: string;
1118
+ comment?: string;
1119
+ }
1120
+
1121
+ export interface HarCookie extends HarNameValue {
1122
+ path?: string;
1123
+ domain?: string;
1124
+ expires?: string;
1125
+ httpOnly?: boolean;
1126
+ secure?: boolean;
1127
+ }
1128
+
1129
+ export interface HarPostDataParam extends HarNameValue {
1130
+ fileName?: string;
1131
+ contentType?: string;
1132
+ }
1133
+
1134
+ export interface HarPostData extends Record<string, unknown> {
1135
+ mimeType: string;
1136
+ params?: HarPostDataParam[];
1137
+ text?: string;
1138
+ comment?: string;
1139
+ }
1140
+
1141
+ export interface HarRequest extends Record<string, unknown> {
1142
+ method: string;
1143
+ url: string;
1144
+ httpVersion: string;
1145
+ cookies: HarCookie[];
1146
+ headers: HarNameValue[];
1147
+ queryString: HarNameValue[];
1148
+ postData?: HarPostData;
1149
+ headersSize: number;
1150
+ bodySize: number;
1151
+ comment?: string;
1152
+ }
1153
+
1154
+ export interface HarContent extends Record<string, unknown> {
1155
+ size: number;
1156
+ compression?: number;
1157
+ mimeType: string;
1158
+ text?: string;
1159
+ encoding?: string;
1160
+ comment?: string;
1161
+ }
1162
+
1163
+ export interface HarResponse extends Record<string, unknown> {
1164
+ status: number;
1165
+ statusText: string;
1166
+ httpVersion: string;
1167
+ cookies: HarCookie[];
1168
+ headers: HarNameValue[];
1169
+ content: HarContent;
1170
+ redirectURL: string;
1171
+ headersSize: number;
1172
+ bodySize: number;
1173
+ comment?: string;
1174
+ }
1175
+
1176
+ export interface HarCacheInfo extends Record<string, unknown> {
1177
+ expires?: string;
1178
+ lastAccess: string;
1179
+ eTag: string;
1180
+ hitCount: number;
1181
+ comment?: string;
1182
+ }
1183
+
1184
+ export interface HarCache extends Record<string, unknown> {
1185
+ beforeRequest?: HarCacheInfo;
1186
+ afterRequest?: HarCacheInfo;
1187
+ comment?: string;
1188
+ }
1189
+
1190
+ export interface HarTimings extends Record<string, unknown> {
1191
+ blocked?: number;
1192
+ dns?: number;
1193
+ connect?: number;
1194
+ send: number;
1195
+ wait: number;
1196
+ receive: number;
1197
+ ssl?: number;
1198
+ comment?: string;
1199
+ }
1200
+
1201
+ export interface HarPageTimings extends Record<string, unknown> {
1202
+ onContentLoad?: number;
1203
+ onLoad?: number;
1204
+ comment?: string;
1205
+ }
1206
+
1207
+ export interface HarPage extends Record<string, unknown> {
1208
+ startedDateTime: string;
1209
+ id: string;
1210
+ title: string;
1211
+ pageTimings: HarPageTimings;
1212
+ comment?: string;
1213
+ }
1214
+
1215
+ export interface HarCreator extends Record<string, unknown> {
1216
+ name: string;
1217
+ version: string;
1218
+ comment?: string;
1219
+ }
1220
+
1221
+ export interface HarEntry extends Record<string, unknown> {
1222
+ pageref?: string;
1223
+ startedDateTime: string;
1224
+ time: number;
1225
+ request: HarRequest;
1226
+ response: HarResponse;
1227
+ cache: HarCache;
1228
+ timings: HarTimings;
1229
+ serverIPAddress?: string;
1230
+ connection?: string;
1231
+ comment?: string;
1232
+ }
1233
+
1234
+ export interface HarLog extends Record<string, unknown> {
1235
+ version: '1.2';
1236
+ creator: HarCreator;
1237
+ browser?: HarCreator;
1238
+ pages?: HarPage[];
1239
+ entries: HarEntry[];
1240
+ comment?: string;
1241
+ }
1242
+
1243
+ export interface HarDocument extends Record<string, unknown> {
1244
+ log: HarLog;
1245
+ }
1246
+
1247
+ export interface HarExportTruncation {
1248
+ reason: 'limit' | 'inline_bytes' | null;
1249
+ limit: number;
1250
+ omitted: number;
1251
+ omittedByLimit: number;
1252
+ omittedBySize: number;
1253
+ }
1254
+
1255
+ export interface HarExportMetadata {
1256
+ format: 'har';
1257
+ harVersion: '1.2';
1258
+ mimeType: 'application/json';
1259
+ byteLength: number;
1260
+ entryCount: number;
1261
+ totalEntries: number;
1262
+ dropped: number;
1263
+ abandoned: number;
1264
+ inflight: number;
1265
+ startedAt: number | null;
1266
+ captureState: HarCaptureState;
1267
+ truncated: boolean;
1268
+ truncation: HarExportTruncation;
1269
+ }
1270
+
1271
+ export type HarExportResult =
1272
+ | (HarExportMetadata & { delivery: 'inline'; har: HarDocument })
1273
+ | (HarExportMetadata & {
1274
+ delivery: 'artifact';
1275
+ artifact: ArtifactDescriptor<'har'>;
1276
+ });
1277
+
1278
+ export interface HarExportEvidence {
1279
+ delivery: HarExportResult['delivery'];
1280
+ entryCount: number;
1281
+ totalEntries: number;
1282
+ dropped: number;
1283
+ abandoned: number;
1284
+ inflight: number;
1285
+ }
1286
+
802
1287
  export type NetworkInterceptAction = 'fulfill' | 'continue' | 'block';
803
1288
 
804
1289
  export interface NetworkInterceptAddParams {
@@ -59,11 +59,13 @@ bbx a11y-tree [maxNodes] [maxDepth] # accessibility tree
59
59
  bbx eval [--await] <expression> # JS eval (--await for promises, - for stdin)
60
60
  bbx console [level] # console output
61
61
  bbx network [limit] # recent fetch/XHR requests
62
+ bbx har [options] [outPath] # export armed CDP capture as sanitized HAR 1.2
62
63
  bbx intercept add <pattern> [--respond <body>] [--status <code>] [--block] # intercept matching requests
63
64
  bbx intercept list|remove <id>|clear # manage interception rules
64
65
  bbx page-text [budget] # full page text
65
- bbx storage [local|session] [keys] # browser storage
66
- bbx perf # performance metrics
66
+ bbx storage [local|session] [keys] # storage key/presence metadata
67
+ bbx call sensitive.read '{"source":"local_storage","key":"name"}' # deliberate exact value
68
+ bbx perf # raw Chrome/CDP counter point sample
67
69
  bbx navigate <url> # navigate to URL
68
70
  bbx reload # reload current page
69
71
  bbx back # navigate back
@@ -145,7 +147,7 @@ After access is enabled:
145
147
  | `DIALOG_ACTION_CONFLICT` | No | Inspect current dialog state; never auto-repeat accept/dismiss |
146
148
  | `TAB_MISMATCH` | No | Tab closed or not found - use `tabs.list` to find an available tab |
147
149
  | `TIMEOUT` | Once | Retry once; if still failing, simplify (smaller `maxNodes`, narrower selector) |
148
- | `RATE_LIMITED` | After 2s | Back off 2 seconds, then retry |
150
+ | `CONTENT_SCRIPT_UNAVAILABLE` | No | Switch to a normal http(s) page in the enabled window |
149
151
  | `EXTENSION_DISCONNECTED` | After 3s | Check Chrome is running; `bbx status` to verify, then retry |
150
152
  | `NATIVE_HOST_UNAVAILABLE` | No | Run `bbx doctor` to diagnose the installation |
151
153
  | `INTERNAL_ERROR` | Once | Retry once; if persistent, check `page.get_console` for details |
@@ -155,6 +157,13 @@ After access is enabled:
155
157
 
156
158
  Error responses now include a machine-readable `error.recovery` field with `retry`, `retryAfterMs`, `alternativeMethod`, and `hint`.
157
159
 
160
+ `health.ping`, `daemon.metrics`, and `bbx doctor` also expose process-local
161
+ five-minute recovery summaries for six fixed, content-free categories. Three
162
+ failures in the same hidden group within 60 seconds marks an active loop. Stop
163
+ repeating the action and follow Doctor's fixed category guidance. Extension
164
+ summaries reset on service-worker restart and daemon summaries on daemon restart;
165
+ they contain no page data, URLs, selectors, payloads, errors, or group names.
166
+
158
167
  ## Core Rules
159
168
 
160
169
  1. **Work in existing tabs** - Never create new tabs unless the user explicitly asks for it, or the task absolutely requires a fresh page (e.g., testing a clean state, comparing across URLs). Prefer `tabs.list` to find an appropriate existing tab.
@@ -168,7 +177,8 @@ Error responses now include a machine-readable `error.recovery` field with `retr
168
177
  9. **Batch reads** - combine independent reads with `bbx batch` in CLI mode or `browser_batch` in MCP mode; do not pass `batch` as a `browser_call` method.
169
178
  10. **Avoid debugger first** - prefer DOM/content-script methods (`dom.*`, `styles.*`, `layout.get_box_model`, `page.get_console`, `page.get_text`, `page.get_storage`, `page.get_network`) before any debugger-backed method. Escalate to CDP only when those cannot answer the question.
170
179
  11. **Evaluate only when needed** - `page.evaluate` is powerful but debugger-backed; use it only when DOM, storage, console, network, or text reads cannot expose the needed state.
171
- 12. **Debugger-backed methods are last resort** - treat `page.evaluate`, `page.handle_dialog`, `dom.get_accessibility_tree`, `page.get_network` with `source: 'cdp'`, input with `executionMode: 'cdp'`, `viewport.resize`, `performance.get_metrics`, `screenshot.capture_*`, and all `cdp.*` methods as escalation steps because they attach `chrome.debugger`.
180
+ 12. **Treat exact storage as sensitive** - use `page.get_storage` for metadata first. Call `sensitive.read` for one exact key only when necessary; never batch or automatically retry it, and never repeat a failed attempt without new evidence.
181
+ 12. **Debugger-backed methods are last resort** - treat `page.evaluate`, `page.handle_dialog`, `dom.get_accessibility_tree`, `page.get_network` with `source: 'cdp'`, input with `executionMode: 'cdp'`, `viewport.resize`, `performance.get_metrics`, `screenshot.capture_*`, and all `cdp.*` methods as escalation steps because they attach `chrome.debugger`. `performance.get_metrics` is a raw browser-maintained CDP counter point sample, not Web Vitals.
172
182
  13. **Wait after change** - after editing source, wait for expected new text, a selector matching the changed attribute, or a detach/attach remount; waiting for `attached` on a selector that already exists does not prove HMR ran. Use `page.wait_for_load_state` for navigation, not HMR.
173
183
  14. **Prime event buffers** - before reproducing a console or fetch/XHR issue, call `page.get_console` and default `page.get_network` once with `clear: true`; then reproduce and read without clearing. CDP all-resource capture instead requires explicit `start`, reproduce, `read`, and `stop` calls.
174
184
  15. **Semantic finding** - use `dom.find_by_text` / `dom.find_by_role` when you know the label but not the selector.
@@ -180,6 +190,8 @@ Error responses now include a machine-readable `error.recovery` field with `retr
180
190
  21. **Do not guess input targets** - for targeted click, focus, type, fill, press-key, checked-state, option-selection, hover, and drag calls, honor actionability errors and returned `resolution` metadata. `cdp_press_key` and `scroll_into_view` use separate contracts. `executionMode` is only `dom` or `cdp`; `input.fill.mode: auto` is a separate DOM setter/keystroke strategy.
181
191
  22. **Stale recovery stays opt-in** - prefer re-querying. Use `recoverStale: true` once only when the same document/URL and a strong unique semantic descriptor should still identify the target; inspect `recovered`, old/new refs, and matched fields. A scan with more than 100 same-tag candidates fails as `ELEMENT_AMBIGUOUS`/`scan_incomplete` because uniqueness is not provable.
182
192
  23. **Dialogs are explicit** - inspect first, then accept or dismiss only when intended. `expectedDialogId` is a pre-dispatch stale-decision check, not an atomic CDP binding; never auto-repeat `DIALOG_ACTION_CONFLICT`.
193
+ 24. **DOM diffs use explicit baselines** - call `dom.baseline.create` before the action, `dom.baseline.compare` afterward, then `dom.baseline.release`. Use a narrow selector and bounded evidence. Never substitute unrelated DOM reads for the retained baseline, and recreate it after `DOM_BASELINE_INVALIDATED`.
194
+ 25. **HAR export reads an armed capture** - use CDP capture `start`, reproduce, `network.export_har`/`bbx har`, then `stop`. Export never starts, stops, or clears capture. Inspect truncation plus `dropped`, `abandoned`, and `inflight` before claiming completeness.
183
195
 
184
196
  ## Token Budget Quick Rules
185
197
 
@@ -251,12 +263,12 @@ bbx page-text 2000 # extract page content
251
263
  | Find | `dom.find_by_text`, `dom.find_by_role`, `dom.wait_for`, `dom.get_accessibility_tree` |
252
264
  | Page State | `page.get_console`, `page.get_storage`, `page.get_text`, `page.wait_for_load_state`, `page.evaluate` (debugger-backed) |
253
265
  | Dialogs | `page.handle_dialog` (debugger-backed; explicit inspect/accept/dismiss only) |
254
- | Network | `page.get_network` (fetch/XHR default; optional CDP lifecycle), `network.intercept.add/remove/list/clear` (debugger-backed) |
266
+ | Network | `page.get_network` (fetch/XHR default; optional CDP lifecycle), `network.export_har`, `network.intercept.add/remove/list/clear` (debugger-backed) |
255
267
  | Interact | `input.click`, `input.type`, `input.fill`, `input.focus`, `input.press_key`, `cdp.dispatch_key_event`, `input.hover`, `input.drag`, `input.scroll_into_view` |
256
268
  | Tabs | `tabs.list` (preferred), `tabs.create` (avoid unless necessary), `tabs.close`, `tabs.activate` |
257
269
  | Patch | `patch.apply_styles`, `patch.apply_dom`, `patch.rollback` |
258
270
  | Navigate | `navigation.navigate`, `viewport.scroll`, `viewport.resize` |
259
- | Performance | `performance.get_metrics` (debugger-backed) |
271
+ | Performance | `performance.get_metrics` (raw Chrome/CDP counter point sample; debugger-backed) |
260
272
  | Escalate | `dom.get_accessibility_tree`, `screenshot.capture_element`, `screenshot.capture_region` (tight crops only), `screenshot.capture_full_page`, `cdp.*` |
261
273
 
262
274
  ## Dev-Server Workflow (HMR-aware)
@@ -336,8 +348,9 @@ Every CLI shortcut command produces consistent `{ok, summary, evidence}` JSON. U
336
348
  - `input.drag` uses `source`, `destination`, and optional destination offsets `offsetX` / `offsetY`.
337
349
  - `executionMode` accepts `dom` or `cdp` and defaults to `dom`; CDP supports only click, hover, drag, type, and fill. `input.fill.mode` (`auto`, `setter`, `keystrokes`) is a separate DOM strategy.
338
350
  - `page.get_network` defaults to low-cost fetch/XHR instrumentation. All-resource CDP capture must be armed before the activity with `source: "cdp", capture: "start"`, read later, and explicitly stopped.
351
+ - `network.export_har` requires that explicit capture to still be armed. Params are `limit` (1-200), sanitized-URL substring `urlPattern`, and `delivery` (`auto`, `inline`, `artifact`). Inline bounds remove whole oldest entries; export never changes capture state. `bbx har` uses the same client to download, length/SHA-256 verify, delete, validate, and atomically write artifacts locally.
339
352
  - `page.handle_dialog` mutations are not atomically bound to `expectedDialogId`; treat `commandDispatched` as dispatch evidence and verify follow-up state.
340
- - Raw `screenshot.capture_region` and `screenshot.capture_full_page` return base64 JSON; prefer `bbx screenshot <ref> [outPath]` when one element is enough.
353
+ - Raw screenshot calls default to inline base64. Use `delivery: "auto"` for size-aware artifact fallback, or explicit `inline` when MCP image content is required; prefer `bbx screenshot <ref> [outPath]` when one element is enough because the CLI downloads and verifies artifacts locally.
341
354
 
342
355
  ## Response Shapes
343
356
 
@@ -360,13 +373,14 @@ Shortcut commands intentionally expose only the common case. Use `bbx call <meth
360
373
  | Computed styles | `result.properties + elementRef` | `Computed N style(s) for ref.` |
361
374
  | Box model | `result.content + border` | `Box model: W×H at (x, y).` |
362
375
  | Network | `result.source + entries` | `Network: N requests.` |
376
+ | HAR export | `result.format === "har"` | `HAR export delivered mode: N/M entries.` |
363
377
  | Console | `entries` (no type field) | `Console: N entries.` |
364
378
  | Logs | `entries[0].at + method` | `Log: N entries.` |
365
379
  | Patch apply | `result.patchId` | `Patch id applied.` |
366
380
  | Patch rollback | `result.rolled_back` | `Patch rolled back.` |
367
381
  | Patch list | `result.patches` | `N active patch(es).` |
368
382
  | HTML | `result.html` | `HTML fragment: N chars.` |
369
- | Performance | `result.metrics` | `Performance: N metrics collected.` |
383
+ | Performance | `result.metrics` | `Raw CDP performance counters: N collected; Web Vitals not measured.` |
370
384
  | Storage | `result.type + count + entries` | `Storage (type): N entries.` |
371
385
  | Click/Focus/Type | `result.clicked/focused/typed` | `Clicked/Focused/Typed ref.` |
372
386
  | Key press | `result.pressed` | `Key pressed (key).` |