@btraut/browser-bridge 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.2] - 2026-02-12
12
+
13
+ ### Fixed
14
+
15
+ - Core debugger bridge now self-heals stale attach state: when a command fails with "Debugger is not attached to the requested tab.", it marks the tab detached, re-attaches once, and retries the command once. It also clears cached attachment state after extension disconnects to avoid false "attached" assumptions that can trigger inspect recovery loops/timeouts.
16
+
17
+ ## [0.7.1] - 2026-02-11
18
+
19
+ ### Fixed
20
+
21
+ - 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.
22
+ - Core error latching: clear drive/inspect/debugger `last_error` state after successful operations so recovered sessions report healthy diagnostics.
23
+
11
24
  ## [0.7.0] - 2026-02-10
12
25
 
13
26
  ### Fixed
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",
@@ -3474,10 +3484,20 @@ var registerArtifactsRoutes = (router, options = {}) => {
3474
3484
  };
3475
3485
 
3476
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
+ };
3477
3495
  var buildDiagnosticReport = (sessionId, context = {}) => {
3478
3496
  const extensionConnected = context.extension?.connected ?? false;
3479
3497
  const debuggerAttached = context.debugger?.attached ?? false;
3480
3498
  const sessionState = context.sessionState;
3499
+ const hasSessionId = Boolean(sessionId);
3500
+ const warnings = [];
3481
3501
  const checks = [
3482
3502
  {
3483
3503
  name: "extension.connected",
@@ -3486,13 +3506,13 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3486
3506
  },
3487
3507
  {
3488
3508
  name: "debugger.attached",
3489
- ok: debuggerAttached,
3490
- 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)."
3491
3511
  },
3492
3512
  {
3493
3513
  name: "session.state",
3494
- ok: Boolean(sessionState),
3495
- 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.",
3496
3516
  details: {
3497
3517
  session_id: sessionId || null,
3498
3518
  state: sessionState ?? "UNKNOWN"
@@ -3500,26 +3520,42 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3500
3520
  }
3501
3521
  ];
3502
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
+ }
3503
3530
  checks.push({
3504
3531
  name: "drive.last_error",
3505
- ok: false,
3532
+ ok: isStale,
3506
3533
  message: context.driveLastError.message,
3507
3534
  details: {
3508
3535
  code: context.driveLastError.code,
3509
3536
  retryable: context.driveLastError.retryable,
3510
- at: context.driveLastError.at
3537
+ at: context.driveLastError.at,
3538
+ ...ageMs !== void 0 ? { age_ms: ageMs } : {}
3511
3539
  }
3512
3540
  });
3513
3541
  }
3514
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
+ }
3515
3550
  checks.push({
3516
3551
  name: "inspect.last_error",
3517
- ok: false,
3552
+ ok: isStale,
3518
3553
  message: context.inspectLastError.message,
3519
3554
  details: {
3520
3555
  code: context.inspectLastError.code,
3521
3556
  retryable: context.inspectLastError.retryable,
3522
- at: context.inspectLastError.at
3557
+ at: context.inspectLastError.at,
3558
+ ...ageMs !== void 0 ? { age_ms: ageMs } : {}
3523
3559
  }
3524
3560
  });
3525
3561
  }
@@ -3578,6 +3614,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
3578
3614
  loop_detected: context.recoveryMetrics.loopDetected
3579
3615
  } : {}
3580
3616
  } : void 0,
3617
+ ...warnings.length > 0 ? { warnings } : {},
3581
3618
  notes: ["Diagnostics include runtime status; some checks may be stubbed."]
3582
3619
  };
3583
3620
  return report;
@@ -4271,6 +4308,7 @@ var DebuggerBridge = class {
4271
4308
  }
4272
4309
  state.attached = true;
4273
4310
  this.touch(tabId, state);
4311
+ this.clearLastError();
4274
4312
  return { ok: true, result: { attached: true } };
4275
4313
  } catch (error) {
4276
4314
  const info = this.handleBridgeError(error);
@@ -4298,6 +4336,7 @@ var DebuggerBridge = class {
4298
4336
  return { ok: false, error };
4299
4337
  }
4300
4338
  this.markDetached(tabId);
4339
+ this.clearLastError();
4301
4340
  return { ok: true, result: { attached: false } };
4302
4341
  } catch (error) {
4303
4342
  const info = this.handleBridgeError(error);
@@ -4311,7 +4350,7 @@ var DebuggerBridge = class {
4311
4350
  return attachResult;
4312
4351
  }
4313
4352
  try {
4314
- const response = await this.bridge.requestDebugger(
4353
+ const runCommand = async () => await this.bridge.requestDebugger(
4315
4354
  "debugger.command",
4316
4355
  {
4317
4356
  tab_id: tabId,
@@ -4320,6 +4359,15 @@ var DebuggerBridge = class {
4320
4359
  },
4321
4360
  timeoutMs
4322
4361
  );
4362
+ let response = await runCommand();
4363
+ if (response.status === "error" && this.shouldRetryAfterStaleAttach(response.error)) {
4364
+ this.markDetached(tabId);
4365
+ const reattach = await this.attach(tabId);
4366
+ if (!reattach.ok) {
4367
+ return reattach;
4368
+ }
4369
+ response = await runCommand();
4370
+ }
4323
4371
  if (response.status === "error") {
4324
4372
  const error = response.error ?? {
4325
4373
  code: "INSPECT_UNAVAILABLE",
@@ -4330,6 +4378,7 @@ var DebuggerBridge = class {
4330
4378
  return { ok: false, error };
4331
4379
  }
4332
4380
  this.touch(tabId, this.ensureTab(tabId));
4381
+ this.clearLastError();
4333
4382
  return { ok: true, result: response.result };
4334
4383
  } catch (error) {
4335
4384
  const info = this.handleBridgeError(error);
@@ -4415,8 +4464,15 @@ var DebuggerBridge = class {
4415
4464
  this.lastError = error;
4416
4465
  this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
4417
4466
  }
4467
+ clearLastError() {
4468
+ this.lastError = void 0;
4469
+ this.lastErrorAt = void 0;
4470
+ }
4418
4471
  handleBridgeError(error) {
4419
4472
  if (error instanceof ExtensionBridgeError) {
4473
+ if (error.code === "EXTENSION_DISCONNECTED") {
4474
+ this.markAllDetached();
4475
+ }
4420
4476
  const info2 = toDriveError(error);
4421
4477
  this.recordError(info2);
4422
4478
  return info2;
@@ -4429,6 +4485,18 @@ var DebuggerBridge = class {
4429
4485
  this.recordError(info);
4430
4486
  return info;
4431
4487
  }
4488
+ shouldRetryAfterStaleAttach(error) {
4489
+ if (!error) return false;
4490
+ if (error.code !== "FAILED_PRECONDITION" && error.code !== "INSPECT_UNAVAILABLE") {
4491
+ return false;
4492
+ }
4493
+ return error.message.toLowerCase().includes("not attached");
4494
+ }
4495
+ markAllDetached() {
4496
+ for (const tabId of this.tabs.keys()) {
4497
+ this.markDetached(tabId);
4498
+ }
4499
+ }
4432
4500
  };
4433
4501
 
4434
4502
  // packages/core/src/server.ts