@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 +13 -0
- package/README.md +15 -0
- package/dist/api.js +56 -11
- package/dist/api.js.map +2 -2
- package/dist/index.js +7 -3
- package/dist/index.js.map +3 -3
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
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:
|
|
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}.` :
|
|
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:
|
|
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:
|
|
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) =>
|
|
4683
|
+
var envelope = (schema) => successEnvelopeSchema(schema);
|
|
4639
4684
|
var TOOL_DEFINITIONS = [
|
|
4640
4685
|
{
|
|
4641
4686
|
name: "session.create",
|