@btraut/browser-bridge 0.6.1 → 0.7.1

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/CHANGELOG.md CHANGED
@@ -8,6 +8,19 @@ The format is based on "Keep a Changelog", and this project adheres to Semantic
8
8
 
9
9
  _TBD_
10
10
 
11
+ ## [0.7.1] - 2026-02-11
12
+
13
+ ### Fixed
14
+
15
+ - Diagnostics doctor: stop failing by default when `session_id` is omitted, treat detached debugger as expected idle behavior, and downgrade stale drive/inspect errors to warnings.
16
+ - Core error latching: clear drive/inspect/debugger `last_error` state after successful operations so recovered sessions report healthy diagnostics.
17
+
18
+ ## [0.7.0] - 2026-02-10
19
+
20
+ ### Fixed
21
+
22
+ - MCP adapter: avoid SDK `_zod` crashes on tool calls by registering object-shaped output schemas and flagging `ok: false` envelopes as MCP errors.
23
+
11
24
  ## [0.6.1] - 2026-02-10
12
25
 
13
26
  ### Added
package/README.md CHANGED
@@ -82,6 +82,21 @@ What makes it different:
82
82
  - **Recovery-first**: sessions have an explicit state machine with `session.recover()` and `diagnostics doctor`.
83
83
  - **Inspect beyond screenshots**: DOM snapshots (AX + HTML) and `inspect dom-diff` to detect page changes.
84
84
 
85
+ ## 🆚 Feature Comparison
86
+
87
+ | Category | Browser Bridge | Playwright MCP | agent-browser | mcp-chrome | Claude Code + Chrome |
88
+ | --- | --- | --- | --- | --- | --- |
89
+ | Uses your real, already-logged-in Chrome (tabs/cookies) | ✅ | ❌ | ❌ | ✅ | ✅ |
90
+ | Visible browser (not headless) | ✅ | ✅ | ❌ | ✅ | ✅ |
91
+ | Per-site permission prompts / allowlist | ✅ | ❌ | ❌ | ❌ | ✅ |
92
+ | Drive/Inspect split (inspect without racing input) | ✅ | ❌ | ❌ | ❌ | ❌ |
93
+ | Token-efficient inspection (element refs, bounded output, cleanup) | ✅ | ❌ | ❌ | ❌ | ❌ |
94
+ | Structured errors + retry hints | ✅ | ❌ | ❌ | ❌ | ❌ |
95
+ | Explicit recovery + doctor-style diagnostics | ✅ | ❌ | ❌ | ❌ | ❌ |
96
+ | DOM diff (change detection) | ✅ | ❌ | ❌ | ❌ | ❌ |
97
+ | HAR / network export | ✅ | ✅ | ✅ | ✅ | ❌ |
98
+ | Open source | ✅ | ✅ | ✅ | ✅ | ❌ |
99
+
85
100
  ## 🔒 Site Permissions (Drive Actions)
86
101
 
87
102
  Browser Bridge is intentionally safe: **drive actions** (`drive.navigate`, click, type, etc.) require **per-site approval**. `inspect.*` is not gated, so agents can inspect first and only ask for permission when it's time to click/type.
package/dist/api.js CHANGED
@@ -848,6 +848,10 @@ var DriveController = class {
848
848
  this.lastError = error;
849
849
  this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
850
850
  }
851
+ clearLastError() {
852
+ this.lastError = void 0;
853
+ this.lastErrorAt = void 0;
854
+ }
851
855
  async execute(sessionId, action, params, timeoutMs) {
852
856
  return await driveMutex.runExclusive(async () => {
853
857
  try {
@@ -888,6 +892,7 @@ var DriveController = class {
888
892
  timeoutMs
889
893
  );
890
894
  if (response.status === "ok") {
895
+ this.clearLastError();
891
896
  return {
892
897
  ok: true,
893
898
  result: response.result
@@ -2800,6 +2805,7 @@ var InspectService = class {
2800
2805
  });
2801
2806
  }
2802
2807
  markInspectConnected(sessionId) {
2808
+ this.clearLastError();
2803
2809
  try {
2804
2810
  const session = this.registry.require(sessionId);
2805
2811
  if (session.state === "INIT" /* INIT */ || session.state === "DRIVE_READY" /* DRIVE_READY */) {
@@ -2814,6 +2820,10 @@ var InspectService = class {
2814
2820
  this.lastError = error;
2815
2821
  this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
2816
2822
  }
2823
+ clearLastError() {
2824
+ this.lastError = void 0;
2825
+ this.lastErrorAt = void 0;
2826
+ }
2817
2827
  buildUnavailableError() {
2818
2828
  return new InspectError(
2819
2829
  "INSPECT_UNAVAILABLE",
@@ -2888,7 +2898,6 @@ var successEnvelopeSchema = (result) => import_zod.z.object({
2888
2898
  ok: import_zod.z.literal(true),
2889
2899
  result
2890
2900
  });
2891
- var apiEnvelopeSchema = (result) => import_zod.z.union([successEnvelopeSchema(result), ErrorEnvelopeSchema]);
2892
2901
 
2893
2902
  // packages/shared/src/schemas.ts
2894
2903
  var import_zod2 = require("zod");
@@ -3475,10 +3484,20 @@ var registerArtifactsRoutes = (router, options = {}) => {
3475
3484
  };
3476
3485
 
3477
3486
  // packages/core/src/diagnostics.ts
3487
+ var STALE_ERROR_THRESHOLD_MS = 2 * 60 * 1e3;
3488
+ var getErrorAgeMs = (timestamp) => {
3489
+ const parsed = Date.parse(timestamp);
3490
+ if (!Number.isFinite(parsed)) {
3491
+ return void 0;
3492
+ }
3493
+ return Math.max(0, Date.now() - parsed);
3494
+ };
3478
3495
  var buildDiagnosticReport = (sessionId, context = {}) => {
3479
3496
  const extensionConnected = context.extension?.connected ?? false;
3480
3497
  const debuggerAttached = context.debugger?.attached ?? false;
3481
3498
  const sessionState = context.sessionState;
3499
+ const hasSessionId = Boolean(sessionId);
3500
+ const warnings = [];
3482
3501
  const checks = [
3483
3502
  {
3484
3503
  name: "extension.connected",
@@ -3487,13 +3506,13 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3487
3506
  },
3488
3507
  {
3489
3508
  name: "debugger.attached",
3490
- ok: debuggerAttached,
3491
- message: debuggerAttached ? "Debugger is attached." : "Debugger is not attached."
3509
+ ok: true,
3510
+ message: debuggerAttached ? "Debugger is attached." : "Debugger is not attached (inspect is idle)."
3492
3511
  },
3493
3512
  {
3494
3513
  name: "session.state",
3495
- ok: Boolean(sessionState),
3496
- message: sessionState ? `Session state is ${sessionState}.` : sessionId ? "Session state unavailable." : "Session id not provided.",
3514
+ ok: hasSessionId ? Boolean(sessionState) : true,
3515
+ message: sessionState ? `Session state is ${sessionState}.` : hasSessionId ? "Session state unavailable." : "Session id not provided.",
3497
3516
  details: {
3498
3517
  session_id: sessionId || null,
3499
3518
  state: sessionState ?? "UNKNOWN"
@@ -3501,26 +3520,42 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3501
3520
  }
3502
3521
  ];
3503
3522
  if (context.driveLastError) {
3523
+ const ageMs = getErrorAgeMs(context.driveLastError.at);
3524
+ const isStale = ageMs !== void 0 && ageMs > STALE_ERROR_THRESHOLD_MS;
3525
+ if (isStale) {
3526
+ warnings.push(
3527
+ `Ignoring stale drive error (${Math.round(ageMs / 1e3)}s old): ${context.driveLastError.message}`
3528
+ );
3529
+ }
3504
3530
  checks.push({
3505
3531
  name: "drive.last_error",
3506
- ok: false,
3532
+ ok: isStale,
3507
3533
  message: context.driveLastError.message,
3508
3534
  details: {
3509
3535
  code: context.driveLastError.code,
3510
3536
  retryable: context.driveLastError.retryable,
3511
- at: context.driveLastError.at
3537
+ at: context.driveLastError.at,
3538
+ ...ageMs !== void 0 ? { age_ms: ageMs } : {}
3512
3539
  }
3513
3540
  });
3514
3541
  }
3515
3542
  if (context.inspectLastError) {
3543
+ const ageMs = getErrorAgeMs(context.inspectLastError.at);
3544
+ const isStale = ageMs !== void 0 && ageMs > STALE_ERROR_THRESHOLD_MS;
3545
+ if (isStale) {
3546
+ warnings.push(
3547
+ `Ignoring stale inspect error (${Math.round(ageMs / 1e3)}s old): ${context.inspectLastError.message}`
3548
+ );
3549
+ }
3516
3550
  checks.push({
3517
3551
  name: "inspect.last_error",
3518
- ok: false,
3552
+ ok: isStale,
3519
3553
  message: context.inspectLastError.message,
3520
3554
  details: {
3521
3555
  code: context.inspectLastError.code,
3522
3556
  retryable: context.inspectLastError.retryable,
3523
- at: context.inspectLastError.at
3557
+ at: context.inspectLastError.at,
3558
+ ...ageMs !== void 0 ? { age_ms: ageMs } : {}
3524
3559
  }
3525
3560
  });
3526
3561
  }
@@ -3579,6 +3614,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3579
3614
  loop_detected: context.recoveryMetrics.loopDetected
3580
3615
  } : {}
3581
3616
  } : void 0,
3617
+ ...warnings.length > 0 ? { warnings } : {},
3582
3618
  notes: ["Diagnostics include runtime status; some checks may be stubbed."]
3583
3619
  };
3584
3620
  return report;
@@ -4272,6 +4308,7 @@ var DebuggerBridge = class {
4272
4308
  }
4273
4309
  state.attached = true;
4274
4310
  this.touch(tabId, state);
4311
+ this.clearLastError();
4275
4312
  return { ok: true, result: { attached: true } };
4276
4313
  } catch (error) {
4277
4314
  const info = this.handleBridgeError(error);
@@ -4299,6 +4336,7 @@ var DebuggerBridge = class {
4299
4336
  return { ok: false, error };
4300
4337
  }
4301
4338
  this.markDetached(tabId);
4339
+ this.clearLastError();
4302
4340
  return { ok: true, result: { attached: false } };
4303
4341
  } catch (error) {
4304
4342
  const info = this.handleBridgeError(error);
@@ -4331,6 +4369,7 @@ var DebuggerBridge = class {
4331
4369
  return { ok: false, error };
4332
4370
  }
4333
4371
  this.touch(tabId, this.ensureTab(tabId));
4372
+ this.clearLastError();
4334
4373
  return { ok: true, result: response.result };
4335
4374
  } catch (error) {
4336
4375
  const info = this.handleBridgeError(error);
@@ -4416,6 +4455,10 @@ var DebuggerBridge = class {
4416
4455
  this.lastError = error;
4417
4456
  this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
4418
4457
  }
4458
+ clearLastError() {
4459
+ this.lastError = void 0;
4460
+ this.lastErrorAt = void 0;
4461
+ }
4419
4462
  handleBridgeError(error) {
4420
4463
  if (error instanceof ExtensionBridgeError) {
4421
4464
  const info2 = toDriveError(error);
@@ -4620,9 +4663,11 @@ var createCoreClient = (options = {}) => {
4620
4663
  var toToolResult = (payload) => {
4621
4664
  const content = [{ type: "text", text: JSON.stringify(payload) }];
4622
4665
  if (payload && typeof payload === "object") {
4666
+ const isErrorEnvelope = ErrorEnvelopeSchema.safeParse(payload).success;
4623
4667
  return {
4624
4668
  content,
4625
- structuredContent: payload
4669
+ structuredContent: payload,
4670
+ isError: isErrorEnvelope
4626
4671
  };
4627
4672
  }
4628
4673
  return { content };
@@ -4635,7 +4680,7 @@ var toInternalErrorEnvelope = (error) => ({
4635
4680
  retryable: false
4636
4681
  }
4637
4682
  });
4638
- var envelope = (schema) => apiEnvelopeSchema(schema);
4683
+ var envelope = (schema) => successEnvelopeSchema(schema);
4639
4684
  var TOOL_DEFINITIONS = [
4640
4685
  {
4641
4686
  name: "session.create",