@btraut/browser-bridge 0.12.0 → 0.13.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.
@@ -68,111 +68,35 @@ body.bb-page.bb-page--popup {
68
68
  padding: 2px;
69
69
  }
70
70
 
71
- .bb-health {
71
+ .bb-connection {
72
72
  border: 1px solid var(--bb-border-2);
73
73
  border-radius: var(--bb-radius-sm);
74
74
  background: var(--bb-bg);
75
75
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
76
76
  padding: 10px 12px;
77
77
  margin: 0 0 10px;
78
- }
79
-
80
- .bb-health-head {
81
78
  display: flex;
82
79
  align-items: center;
83
80
  justify-content: space-between;
84
- margin-bottom: 8px;
85
- }
86
-
87
- .bb-health-label {
88
- font-size: 12px;
89
- font-weight: 700;
90
- letter-spacing: 0.04em;
91
- color: var(--bb-ink-2);
92
- text-transform: uppercase;
93
- }
94
-
95
- .bb-health-state {
96
- border: 1px solid var(--bb-border);
97
- border-radius: 999px;
98
- padding: 2px 8px;
99
- font-size: 11px;
100
- font-weight: 700;
101
- text-transform: capitalize;
102
- color: var(--bb-ink);
103
- background: rgba(0, 0, 0, 0.04);
104
- }
105
-
106
- .bb-health-state[data-state='connected'] {
107
- color: #1f6f3f;
108
- border-color: rgba(31, 111, 63, 0.35);
109
- background: rgba(31, 111, 63, 0.12);
110
81
  }
111
82
 
112
- .bb-health-state[data-state='connecting'],
113
- .bb-health-state[data-state='backoff'] {
114
- color: #8a5200;
115
- border-color: rgba(138, 82, 0, 0.35);
116
- background: rgba(138, 82, 0, 0.12);
117
- }
118
-
119
- .bb-health-state[data-state='disconnected'] {
120
- color: #a22b2b;
121
- border-color: rgba(162, 43, 43, 0.35);
122
- background: rgba(162, 43, 43, 0.12);
123
- }
124
-
125
- .bb-health-grid {
126
- display: grid;
127
- grid-template-columns: auto 1fr;
128
- gap: 6px 8px;
129
- margin: 0;
130
- }
131
-
132
- .bb-health-grid dt {
133
- margin: 0;
134
- font-size: 11px;
135
- color: var(--bb-ink-2);
136
- }
137
-
138
- .bb-health-grid dd {
139
- margin: 0;
140
- font-size: 12px;
141
- color: var(--bb-ink);
142
- overflow-wrap: anywhere;
143
- }
144
-
145
- .bb-health-error {
146
- min-height: 14px;
147
- margin: 8px 0 0;
148
- font-size: 11px;
149
- color: #a22b2b;
150
- }
151
-
152
- .bb-health-actions {
153
- margin-top: 6px;
154
- display: flex;
155
- align-items: center;
156
- gap: 8px;
157
- }
158
-
159
- .bb-health-copy {
160
- border: 1px solid var(--bb-border);
161
- border-radius: 8px;
162
- background: var(--bb-bg);
83
+ .bb-connection-label {
84
+ font-size: 14px;
85
+ font-weight: 600;
163
86
  color: var(--bb-ink);
164
- padding: 5px 8px;
165
- font-size: 12px;
166
- cursor: pointer;
167
87
  }
168
88
 
169
- .bb-health-copy:hover {
170
- background: var(--bb-hover);
89
+ .bb-connection-dot {
90
+ width: 11px;
91
+ height: 11px;
92
+ border-radius: 999px;
93
+ border: 1px solid rgba(162, 43, 43, 0.35);
94
+ background: #c93939;
171
95
  }
172
96
 
173
- .bb-health-copy-status {
174
- font-size: 11px;
175
- color: var(--bb-ink-2);
97
+ .bb-connection-dot[data-connected='true'] {
98
+ border-color: rgba(31, 111, 63, 0.4);
99
+ background: #2b9a52;
176
100
  }
177
101
 
178
102
  .bb-popup-head {
@@ -1,3 +1,57 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // packages/shared/dist/contract-version.js
28
+ var require_contract_version = __commonJS({
29
+ "packages/shared/dist/contract-version.js"(exports) {
30
+ "use strict";
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.resolveContractVersionMismatch = exports.DRIVE_WS_PROTOCOL_VERSION = exports.HTTP_CONTRACT_VERSION = exports.HTTP_CONTRACT_VERSION_HEADER = void 0;
33
+ exports.HTTP_CONTRACT_VERSION_HEADER = "x-browser-bridge-contract-version";
34
+ exports.HTTP_CONTRACT_VERSION = "2026-02-17.1";
35
+ exports.DRIVE_WS_PROTOCOL_VERSION = "2026-02-17.1";
36
+ var resolveContractVersionMismatch = (receivedVersion) => {
37
+ if (!receivedVersion || receivedVersion.trim().length === 0) {
38
+ return void 0;
39
+ }
40
+ if (receivedVersion === exports.HTTP_CONTRACT_VERSION) {
41
+ return void 0;
42
+ }
43
+ return {
44
+ expected: exports.HTTP_CONTRACT_VERSION,
45
+ received: receivedVersion
46
+ };
47
+ };
48
+ exports.resolveContractVersionMismatch = resolveContractVersionMismatch;
49
+ }
50
+ });
51
+
52
+ // packages/extension/src/background.ts
53
+ var import_contract_version = __toESM(require_contract_version());
54
+
1
55
  // packages/extension/src/error-sanitizer.ts
2
56
  var TRAILING_PUNCTUATION_RE = /[.,;:!?]+$/;
3
57
  var stripTrailingPunctuation = (value) => {
@@ -97,7 +151,9 @@ var SITE_ALLOWLIST_KEY = "siteAllowlist";
97
151
  var PERMISSION_PROMPT_WAIT_MS_KEY = "permissionPromptWaitMs";
98
152
  var DEFAULT_PERMISSION_PROMPT_WAIT_MS = 3e4;
99
153
  var SITE_PERMISSIONS_MODE_KEY = "sitePermissionsMode";
154
+ var DEBUGGER_CAPABILITY_ENABLED_KEY = "debuggerCapabilityEnabled";
100
155
  var DEFAULT_SITE_PERMISSIONS_MODE = "granular";
156
+ var DEFAULT_DEBUGGER_CAPABILITY_ENABLED = false;
101
157
  var siteKeyFromUrl = (rawUrl) => {
102
158
  if (!rawUrl || typeof rawUrl !== "string") {
103
159
  return null;
@@ -199,6 +255,27 @@ var readPermissionPromptWaitMs = async () => {
199
255
  );
200
256
  });
201
257
  };
258
+ var readDebuggerCapabilityEnabled = async () => {
259
+ return await new Promise((resolve) => {
260
+ chrome.storage.local.get(
261
+ [DEBUGGER_CAPABILITY_ENABLED_KEY],
262
+ (result) => {
263
+ const raw = result?.[DEBUGGER_CAPABILITY_ENABLED_KEY];
264
+ if (typeof raw === "boolean") {
265
+ resolve(raw);
266
+ return;
267
+ }
268
+ try {
269
+ chrome.storage.local.set({
270
+ [DEBUGGER_CAPABILITY_ENABLED_KEY]: DEFAULT_DEBUGGER_CAPABILITY_ENABLED
271
+ });
272
+ } catch {
273
+ }
274
+ resolve(DEFAULT_DEBUGGER_CAPABILITY_ENABLED);
275
+ }
276
+ );
277
+ });
278
+ };
202
279
  var isSiteAllowed = async (siteKey) => {
203
280
  const key = normalizeSiteKey(siteKey);
204
281
  const allowlist = await readAllowlistRaw();
@@ -488,6 +565,8 @@ var ConnectionStateTracker = class {
488
565
  var DEFAULT_CORE_PORT = 3210;
489
566
  var CORE_PORT_KEY = "corePort";
490
567
  var CORE_WS_PATH = "/drive";
568
+ var CORE_HEALTH_PATH = "/health";
569
+ var CORE_HEALTH_TIMEOUT_MS = 1200;
491
570
  var DEBUGGER_PROTOCOL_VERSION = "1.3";
492
571
  var DEBUGGER_IDLE_TIMEOUT_KEY = "debuggerIdleTimeoutMs";
493
572
  var DEFAULT_DEBUGGER_IDLE_TIMEOUT_MS = 15e3;
@@ -499,9 +578,62 @@ var HISTORY_POST_NAV_DOM_GRACE_TIMEOUT_MS = 2e3;
499
578
  var AGENT_TAB_ID_KEY = "agentTabId";
500
579
  var AGENT_TAB_GROUP_TITLE = "Browser Bridge";
501
580
  var AGENT_TAB_BOOTSTRAP_PATH = "agent-tab.html";
581
+ var AGENT_TAB_FAVICON_ASSET_PATH = "assets/icons/icon-32.png";
582
+ var AGENT_TAB_BRANDING_ACTION = "drive.agent_tab_branding";
583
+ var AGENT_TAB_GROUP_RETRY_DELAYS_MS = [0, 120, 300];
584
+ var AGENT_TAB_BRANDING_TIMEOUT_MS = 1500;
585
+ var BASE_NEGOTIATED_CAPABILITIES = Object.freeze({
586
+ "drive.navigate": true,
587
+ "drive.go_back": true,
588
+ "drive.go_forward": true,
589
+ "drive.click": true,
590
+ "drive.hover": true,
591
+ "drive.select": true,
592
+ "drive.type": true,
593
+ "drive.fill_form": true,
594
+ "drive.drag": true,
595
+ "drive.handle_dialog": true,
596
+ "drive.key": true,
597
+ "drive.key_press": true,
598
+ "drive.scroll": true,
599
+ "drive.screenshot": true,
600
+ "drive.wait_for": true,
601
+ "drive.tab_list": true,
602
+ "drive.tab_activate": true,
603
+ "drive.tab_close": true,
604
+ "drive.ping": true
605
+ });
606
+ var DEBUGGER_CAPABILITY_ACTIONS = [
607
+ "debugger.attach",
608
+ "debugger.detach",
609
+ "debugger.command"
610
+ ];
611
+ var buildNegotiatedCapabilities = (debuggerCapabilityEnabled) => {
612
+ const capabilities = {
613
+ ...BASE_NEGOTIATED_CAPABILITIES
614
+ };
615
+ for (const action of DEBUGGER_CAPABILITY_ACTIONS) {
616
+ capabilities[action] = debuggerCapabilityEnabled;
617
+ }
618
+ return capabilities;
619
+ };
620
+ var debuggerCapabilityDisabledError = () => {
621
+ return {
622
+ code: "ATTACH_DENIED",
623
+ message: "Debugger capability is disabled. Enable debugger-based inspect in extension options and retry.",
624
+ retryable: false,
625
+ details: {
626
+ reason: "debugger_capability_disabled",
627
+ next_step: "Open Browser Bridge extension options, enable debugger-based inspect, then retry."
628
+ }
629
+ };
630
+ };
502
631
  var getAgentTabBootstrapUrl = () => {
503
632
  return typeof chrome.runtime?.getURL === "function" ? chrome.runtime.getURL(AGENT_TAB_BOOTSTRAP_PATH) : AGENT_TAB_BOOTSTRAP_PATH;
504
633
  };
634
+ var getAgentTabFaviconUrl = () => {
635
+ return typeof chrome.runtime?.getURL === "function" ? chrome.runtime.getURL(AGENT_TAB_FAVICON_ASSET_PATH) : AGENT_TAB_FAVICON_ASSET_PATH;
636
+ };
505
637
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
506
638
  var makeEventId = /* @__PURE__ */ (() => {
507
639
  let counter = 0;
@@ -870,23 +1002,38 @@ var ensureAgentTabGroup = async (tabId, windowId) => {
870
1002
  if (!chrome.tabGroups || typeof chrome.tabGroups.update !== "function") {
871
1003
  return;
872
1004
  }
873
- try {
874
- const groupId = await wrapChromeCallback(
875
- (callback) => chrome.tabs.group(
876
- { tabIds: tabId, createProperties: { windowId } },
877
- callback
878
- )
879
- );
880
- await wrapChromeVoid(
881
- (callback) => chrome.tabGroups.update(
882
- groupId,
883
- { title: AGENT_TAB_GROUP_TITLE },
884
- () => callback()
885
- )
886
- );
887
- } catch (error) {
888
- console.debug("Failed to create/update agent tab group.", error);
1005
+ let lastError;
1006
+ for (const retryDelayMs of AGENT_TAB_GROUP_RETRY_DELAYS_MS) {
1007
+ if (retryDelayMs > 0) {
1008
+ await delayMs(retryDelayMs);
1009
+ }
1010
+ try {
1011
+ const groupId = await wrapChromeCallback(
1012
+ (callback) => chrome.tabs.group(
1013
+ { tabIds: tabId, createProperties: { windowId } },
1014
+ callback
1015
+ )
1016
+ );
1017
+ await wrapChromeVoid(
1018
+ (callback) => chrome.tabGroups.update(
1019
+ groupId,
1020
+ { title: AGENT_TAB_GROUP_TITLE },
1021
+ () => callback()
1022
+ )
1023
+ );
1024
+ return;
1025
+ } catch (error) {
1026
+ lastError = error;
1027
+ }
1028
+ }
1029
+ console.debug("Failed to create/update agent tab group.", lastError);
1030
+ };
1031
+ var ensureAgentTabGroupForTab = async (tabId, tab) => {
1032
+ const windowId = tab.windowId;
1033
+ if (typeof windowId !== "number") {
1034
+ return;
889
1035
  }
1036
+ await ensureAgentTabGroup(tabId, windowId);
890
1037
  };
891
1038
  var createAgentWindow = async () => {
892
1039
  const created = await wrapChromeCallback(
@@ -941,6 +1088,8 @@ var getOrCreateAgentTabId = async () => {
941
1088
  if (typeof url === "string" && isRestrictedUrl(url)) {
942
1089
  throw new Error(`Agent tab points at restricted URL: ${url}`);
943
1090
  }
1091
+ await ensureAgentTabGroupForTab(agentTabId, tab);
1092
+ void refreshAgentTabBranding(agentTabId);
944
1093
  return agentTabId;
945
1094
  } catch {
946
1095
  clearAgentTarget();
@@ -957,6 +1106,8 @@ var getOrCreateAgentTabId = async () => {
957
1106
  agentTabId = stored;
958
1107
  ensureLastActiveAt(stored);
959
1108
  markTabActive(stored);
1109
+ await ensureAgentTabGroupForTab(stored, tab);
1110
+ void refreshAgentTabBranding(stored);
960
1111
  return stored;
961
1112
  } catch {
962
1113
  await writeAgentTabId(null);
@@ -1062,6 +1213,17 @@ var sendToTab = async (tabId, action, params, options) => {
1062
1213
  }
1063
1214
  };
1064
1215
  };
1216
+ var refreshAgentTabBranding = async (tabId) => {
1217
+ const result = await sendToTab(
1218
+ tabId,
1219
+ AGENT_TAB_BRANDING_ACTION,
1220
+ { favicon_url: getAgentTabFaviconUrl() },
1221
+ { timeoutMs: AGENT_TAB_BRANDING_TIMEOUT_MS }
1222
+ );
1223
+ if (!result.ok) {
1224
+ return;
1225
+ }
1226
+ };
1065
1227
  var waitForHistoryNavigationSignal = async (tabId, timeoutMs) => {
1066
1228
  return await new Promise((resolve, reject) => {
1067
1229
  let timeout;
@@ -1153,6 +1315,7 @@ var getWsEndpoint = async () => {
1153
1315
  url: `ws://${endpoint.host}:${endpoint.port}${CORE_WS_PATH}`
1154
1316
  };
1155
1317
  };
1318
+ var getHealthEndpoint = (endpoint) => `http://${endpoint.host}:${endpoint.port}${CORE_HEALTH_PATH}`;
1156
1319
  var DriveSocket = class {
1157
1320
  constructor() {
1158
1321
  this.socket = null;
@@ -1209,6 +1372,16 @@ var DriveSocket = class {
1209
1372
  async connect() {
1210
1373
  const { endpoint, url } = await getWsEndpoint();
1211
1374
  this.connection.setEndpoint(endpoint);
1375
+ const health = await this.checkCoreHealth(endpoint);
1376
+ if (!health.ok) {
1377
+ this.connection.markDisconnected();
1378
+ this.recordConnectionFailure(
1379
+ "core unavailable",
1380
+ new Error(health.detail)
1381
+ );
1382
+ this.scheduleReconnect();
1383
+ return;
1384
+ }
1212
1385
  try {
1213
1386
  const socket2 = new WebSocket(url);
1214
1387
  this.socket = socket2;
@@ -1241,9 +1414,47 @@ var DriveSocket = class {
1241
1414
  this.scheduleReconnect();
1242
1415
  }
1243
1416
  }
1417
+ async checkCoreHealth(endpoint) {
1418
+ const controller = new AbortController();
1419
+ const timeoutId = self.setTimeout(() => {
1420
+ controller.abort();
1421
+ }, CORE_HEALTH_TIMEOUT_MS);
1422
+ try {
1423
+ const response = await fetch(getHealthEndpoint(endpoint), {
1424
+ method: "GET",
1425
+ cache: "no-store",
1426
+ signal: controller.signal
1427
+ });
1428
+ if (!response.ok) {
1429
+ return {
1430
+ ok: false,
1431
+ detail: `health returned HTTP ${response.status}`
1432
+ };
1433
+ }
1434
+ return { ok: true, detail: "ok" };
1435
+ } catch (error) {
1436
+ if (error instanceof DOMException && error.name === "AbortError") {
1437
+ return {
1438
+ ok: false,
1439
+ detail: `health timed out after ${CORE_HEALTH_TIMEOUT_MS}ms`
1440
+ };
1441
+ }
1442
+ return {
1443
+ ok: false,
1444
+ detail: error instanceof Error && error.message.length > 0 ? error.message : "health check failed"
1445
+ };
1446
+ } finally {
1447
+ clearTimeout(timeoutId);
1448
+ }
1449
+ }
1244
1450
  getConnectionStatus() {
1245
1451
  return this.connection.getStatus();
1246
1452
  }
1453
+ refreshCapabilities() {
1454
+ void this.sendHello().catch((error) => {
1455
+ console.error("DriveSocket refreshCapabilities failed:", error);
1456
+ });
1457
+ }
1247
1458
  handleSocketUnavailable(socket2, reason) {
1248
1459
  if (this.socket !== socket2) {
1249
1460
  return;
@@ -1273,6 +1484,7 @@ var DriveSocket = class {
1273
1484
  async sendHello() {
1274
1485
  const manifest = chrome.runtime.getManifest();
1275
1486
  const endpoint = await readCoreEndpointConfig();
1487
+ const debuggerCapabilityEnabled = await readDebuggerCapabilityEnabled();
1276
1488
  let tabs = [];
1277
1489
  try {
1278
1490
  tabs = await queryTabs();
@@ -1282,6 +1494,8 @@ var DriveSocket = class {
1282
1494
  }
1283
1495
  const params = {
1284
1496
  version: manifest.version,
1497
+ protocol_version: import_contract_version.DRIVE_WS_PROTOCOL_VERSION,
1498
+ capabilities: buildNegotiatedCapabilities(debuggerCapabilityEnabled),
1285
1499
  core_host: endpoint.host,
1286
1500
  core_port: endpoint.port,
1287
1501
  core_port_source: endpoint.portSource,
@@ -1356,6 +1570,19 @@ var DriveSocket = class {
1356
1570
  });
1357
1571
  }
1358
1572
  }
1573
+ async refreshDebuggerCapabilityState() {
1574
+ const enabled = await readDebuggerCapabilityEnabled();
1575
+ if (!enabled) {
1576
+ await this.detachAllDebuggerSessions();
1577
+ }
1578
+ this.refreshCapabilities();
1579
+ }
1580
+ async handleDebuggerCapabilityChange(enabled) {
1581
+ if (!enabled) {
1582
+ await this.detachAllDebuggerSessions();
1583
+ }
1584
+ this.refreshCapabilities();
1585
+ }
1359
1586
  async handleRequest(message) {
1360
1587
  let driveMessage = null;
1361
1588
  let gatedSiteKey = null;
@@ -1394,6 +1621,15 @@ var DriveSocket = class {
1394
1621
  return;
1395
1622
  }
1396
1623
  if (message.action.startsWith("debugger.")) {
1624
+ if (!await readDebuggerCapabilityEnabled()) {
1625
+ this.sendMessage({
1626
+ id: message.id,
1627
+ action: message.action,
1628
+ status: "error",
1629
+ error: sanitizeDriveErrorInfo(debuggerCapabilityDisabledError())
1630
+ });
1631
+ return;
1632
+ }
1397
1633
  await this.handleDebuggerRequest(message);
1398
1634
  return;
1399
1635
  }
@@ -1405,8 +1641,6 @@ var DriveSocket = class {
1405
1641
  "drive.navigate",
1406
1642
  "drive.go_back",
1407
1643
  "drive.go_forward",
1408
- "drive.back",
1409
- "drive.forward",
1410
1644
  "drive.click",
1411
1645
  "drive.hover",
1412
1646
  "drive.select",
@@ -1599,13 +1833,14 @@ var DriveSocket = class {
1599
1833
  return;
1600
1834
  }
1601
1835
  }
1836
+ if (tabId === agentTabId) {
1837
+ void refreshAgentTabBranding(tabId);
1838
+ }
1602
1839
  respondOk({ ok: true });
1603
1840
  return;
1604
1841
  }
1605
1842
  case "drive.go_back":
1606
- case "drive.back":
1607
- case "drive.go_forward":
1608
- case "drive.forward": {
1843
+ case "drive.go_forward": {
1609
1844
  const params = message.params ?? {};
1610
1845
  let tabId = params.tab_id;
1611
1846
  if (tabId !== void 0 && typeof tabId !== "number") {
@@ -1651,6 +1886,9 @@ var DriveSocket = class {
1651
1886
  return;
1652
1887
  }
1653
1888
  }
1889
+ if (tabId === agentTabId) {
1890
+ void refreshAgentTabBranding(tabId);
1891
+ }
1654
1892
  respondOk({ ok: true });
1655
1893
  return;
1656
1894
  }
@@ -3036,6 +3274,9 @@ var DriveSocket = class {
3036
3274
  }
3037
3275
  }
3038
3276
  async handleDebuggerEvent(source, method, params) {
3277
+ if (!await readDebuggerCapabilityEnabled()) {
3278
+ return;
3279
+ }
3039
3280
  const tabId = source.tabId;
3040
3281
  if (typeof tabId !== "number") {
3041
3282
  return;
@@ -3054,6 +3295,9 @@ var DriveSocket = class {
3054
3295
  return;
3055
3296
  }
3056
3297
  this.clearDebuggerSession(tabId);
3298
+ if (!await readDebuggerCapabilityEnabled()) {
3299
+ return;
3300
+ }
3057
3301
  this.sendDebuggerEvent({
3058
3302
  tab_id: tabId,
3059
3303
  method: "Debugger.detached",
@@ -3192,6 +3436,15 @@ var DriveSocket = class {
3192
3436
  }
3193
3437
  return null;
3194
3438
  }
3439
+ async detachAllDebuggerSessions() {
3440
+ const tabIds = Array.from(this.debuggerSessions.keys());
3441
+ for (const tabId of tabIds) {
3442
+ const error = await this.detachDebugger(tabId);
3443
+ if (error) {
3444
+ console.warn("DriveSocket detachDebugger failed:", tabId, error);
3445
+ }
3446
+ }
3447
+ }
3195
3448
  async sendDebuggerCommand(tabId, method, params, timeoutMs) {
3196
3449
  return await new Promise((resolve, reject) => {
3197
3450
  let finished = false;
@@ -3322,14 +3575,53 @@ chrome.debugger.onDetach.addListener(
3322
3575
  );
3323
3576
  chrome.runtime.onMessage.addListener(
3324
3577
  (message, _sender, sendResponse) => {
3325
- if (!message || typeof message !== "object" || message.action !== "drive.connection_status") {
3578
+ if (!message || typeof message !== "object") {
3326
3579
  return void 0;
3327
3580
  }
3328
- sendResponse({
3329
- ok: true,
3330
- result: socket.getConnectionStatus()
3581
+ const action = message.action;
3582
+ if (action === "drive.connection_status") {
3583
+ sendResponse({
3584
+ ok: true,
3585
+ result: socket.getConnectionStatus()
3586
+ });
3587
+ return true;
3588
+ }
3589
+ if (action === "drive.refresh_capabilities") {
3590
+ void socket.refreshDebuggerCapabilityState().then(() => {
3591
+ sendResponse({ ok: true, result: { refreshed: true } });
3592
+ }).catch((error) => {
3593
+ const message2 = error instanceof Error ? error.message : "Failed to refresh capabilities.";
3594
+ sendResponse({ ok: false, error: { message: message2 } });
3595
+ });
3596
+ return true;
3597
+ }
3598
+ return void 0;
3599
+ }
3600
+ );
3601
+ chrome.storage.onChanged.addListener(
3602
+ (changes, areaName) => {
3603
+ if (areaName !== "local") {
3604
+ return;
3605
+ }
3606
+ const debuggerChange = changes[DEBUGGER_CAPABILITY_ENABLED_KEY];
3607
+ if (!debuggerChange) {
3608
+ return;
3609
+ }
3610
+ if (typeof debuggerChange.newValue === "boolean") {
3611
+ void socket.handleDebuggerCapabilityChange(debuggerChange.newValue).catch((error) => {
3612
+ console.error(
3613
+ "DriveSocket handleDebuggerCapabilityChange failed:",
3614
+ error
3615
+ );
3616
+ });
3617
+ return;
3618
+ }
3619
+ void socket.refreshDebuggerCapabilityState().catch((error) => {
3620
+ console.error(
3621
+ "DriveSocket refreshDebuggerCapabilityState failed:",
3622
+ error
3623
+ );
3331
3624
  });
3332
- return true;
3333
3625
  }
3334
3626
  );
3335
3627
  socket.start();