@opentabs-dev/browser-extension 0.0.52 → 0.0.54

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 (88) hide show
  1. package/dist/background-message-handlers.d.ts +33 -6
  2. package/dist/background-message-handlers.d.ts.map +1 -1
  3. package/dist/background-message-handlers.js +335 -20
  4. package/dist/background-message-handlers.js.map +1 -1
  5. package/dist/background.js +859 -365
  6. package/dist/background.js.map +1 -1
  7. package/dist/browser-commands/extension-commands.d.ts.map +1 -1
  8. package/dist/browser-commands/extension-commands.js +5 -2
  9. package/dist/browser-commands/extension-commands.js.map +1 -1
  10. package/dist/browser-commands/key-press-command.d.ts +2 -0
  11. package/dist/browser-commands/key-press-command.d.ts.map +1 -1
  12. package/dist/browser-commands/key-press-command.js +42 -4
  13. package/dist/browser-commands/key-press-command.js.map +1 -1
  14. package/dist/confirmation-badge.d.ts +17 -3
  15. package/dist/confirmation-badge.d.ts.map +1 -1
  16. package/dist/confirmation-badge.js +55 -18
  17. package/dist/confirmation-badge.js.map +1 -1
  18. package/dist/constants.d.ts +8 -0
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +8 -0
  21. package/dist/constants.js.map +1 -1
  22. package/dist/dispatch-helpers.d.ts +1 -1
  23. package/dist/extension-messages.d.ts +48 -21
  24. package/dist/extension-messages.d.ts.map +1 -1
  25. package/dist/known-methods.d.ts +2 -2
  26. package/dist/known-methods.d.ts.map +1 -1
  27. package/dist/known-methods.js +1 -2
  28. package/dist/known-methods.js.map +1 -1
  29. package/dist/message-router.d.ts.map +1 -1
  30. package/dist/message-router.js +163 -24
  31. package/dist/message-router.js.map +1 -1
  32. package/dist/offscreen/index.js +17 -7
  33. package/dist/offscreen/index.js.map +1 -1
  34. package/dist/server-request.d.ts +19 -0
  35. package/dist/server-request.d.ts.map +1 -0
  36. package/dist/server-request.js +79 -0
  37. package/dist/server-request.js.map +1 -0
  38. package/dist/server-state-cache.d.ts +68 -0
  39. package/dist/server-state-cache.d.ts.map +1 -0
  40. package/dist/server-state-cache.js +208 -0
  41. package/dist/server-state-cache.js.map +1 -0
  42. package/dist/side-panel/App.d.ts.map +1 -1
  43. package/dist/side-panel/App.js +60 -58
  44. package/dist/side-panel/App.js.map +1 -1
  45. package/dist/side-panel/bridge.d.ts +31 -34
  46. package/dist/side-panel/bridge.d.ts.map +1 -1
  47. package/dist/side-panel/bridge.js +27 -91
  48. package/dist/side-panel/bridge.js.map +1 -1
  49. package/dist/side-panel/components/BrowserToolsCard.d.ts.map +1 -1
  50. package/dist/side-panel/components/BrowserToolsCard.js +2 -1
  51. package/dist/side-panel/components/BrowserToolsCard.js.map +1 -1
  52. package/dist/side-panel/components/ConfirmationDialog.d.ts +7 -2
  53. package/dist/side-panel/components/ConfirmationDialog.d.ts.map +1 -1
  54. package/dist/side-panel/components/ConfirmationDialog.js.map +1 -1
  55. package/dist/side-panel/components/PluginCard.d.ts.map +1 -1
  56. package/dist/side-panel/components/PluginCard.js +2 -1
  57. package/dist/side-panel/components/PluginCard.js.map +1 -1
  58. package/dist/side-panel/components/PluginIcon.d.ts +2 -1
  59. package/dist/side-panel/components/PluginIcon.d.ts.map +1 -1
  60. package/dist/side-panel/components/PluginIcon.js +23 -5
  61. package/dist/side-panel/components/PluginIcon.js.map +1 -1
  62. package/dist/side-panel/components/ToolIcon.d.ts +2 -1
  63. package/dist/side-panel/components/ToolIcon.d.ts.map +1 -1
  64. package/dist/side-panel/components/ToolIcon.js +17 -3
  65. package/dist/side-panel/components/ToolIcon.js.map +1 -1
  66. package/dist/side-panel/components/ToolRow.d.ts.map +1 -1
  67. package/dist/side-panel/components/ToolRow.js +1 -2
  68. package/dist/side-panel/components/ToolRow.js.map +1 -1
  69. package/dist/side-panel/components/retro/Button.d.ts +1 -1
  70. package/dist/side-panel/hooks/useServerNotifications.d.ts +1 -3
  71. package/dist/side-panel/hooks/useServerNotifications.d.ts.map +1 -1
  72. package/dist/side-panel/hooks/useServerNotifications.js +15 -11
  73. package/dist/side-panel/hooks/useServerNotifications.js.map +1 -1
  74. package/dist/side-panel/side-panel.js +1254 -1218
  75. package/dist/side-panel/styles.css +1 -1
  76. package/dist/side-panel-toggle.d.ts +6 -0
  77. package/dist/side-panel-toggle.d.ts.map +1 -1
  78. package/dist/side-panel-toggle.js +6 -0
  79. package/dist/side-panel-toggle.js.map +1 -1
  80. package/dist/tab-state.d.ts +26 -1
  81. package/dist/tab-state.d.ts.map +1 -1
  82. package/dist/tab-state.js +163 -11
  83. package/dist/tab-state.js.map +1 -1
  84. package/dist/tool-dispatch.d.ts.map +1 -1
  85. package/dist/tool-dispatch.js +3 -2
  86. package/dist/tool-dispatch.js.map +1 -1
  87. package/manifest.json +1 -1
  88. package/package.json +1 -1
@@ -1,5 +1,99 @@
1
+ // dist/constants.js
2
+ var KEEPALIVE_ALARM = "opentabs-keepalive";
3
+ var KEEPALIVE_INTERVAL_MINUTES = 0.5;
4
+ var PLUGINS_META_KEY = "plugins_meta";
5
+ var WS_CONNECTED_KEY = "wsConnected";
6
+ var SIDE_PANEL_OPEN_WINDOWS_KEY = "sidePanelOpenWindows";
7
+ var SCRIPT_TIMEOUT_MS = 25e3;
8
+ var MAX_SCRIPT_TIMEOUT_MS = 295e3;
9
+ var IS_READY_TIMEOUT_MS = 5e3;
10
+ var READINESS_POLL_INTERVAL_MS = 3e4;
11
+ var RELOAD_FLUSH_DELAY_MS = 100;
12
+ var INJECTION_RETRY_DELAY_MS = 200;
13
+ var SCREENSHOT_RENDER_DELAY_MS = 100;
14
+ var WS_FLUSH_DELAY_MS = 50;
15
+ var SERVER_PORT_KEY = "serverPort";
16
+ var DEFAULT_SERVER_PORT = 9515;
17
+ var TEXT_PREVIEW_MAX_LENGTH = 200;
18
+ var DEFAULT_WAIT_TIMEOUT_MS = 1e4;
19
+ var POLL_INTERVAL_MS = 100;
20
+ var DEFAULT_QUERY_LIMIT = 100;
21
+ var MAX_INPUT_SIZE = 10 * 1024 * 1024;
22
+ var SIDE_PANEL_TIMEOUT_MS = 3e3;
23
+ var CDP_VERSION = "1.3";
24
+ var EXEC_MAX_ASYNC_WAIT_MS = 1e4;
25
+ var EXEC_POLL_INTERVAL_MS = 50;
26
+ var EXEC_RESULT_TRUNCATION_LIMIT = 5e4;
27
+ var DEFAULT_LOG_LIMIT = 100;
28
+ var VALID_PLUGIN_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
29
+ var isValidPluginName = (name) => VALID_PLUGIN_NAME.test(name);
30
+ var buildWsUrl = (port) => `ws://localhost:${port}/ws`;
31
+
32
+ // dist/side-panel-toggle.js
33
+ var openWindows = /* @__PURE__ */ new Set();
34
+ var persistOpenWindows = () => {
35
+ chrome.storage.session.set({ [SIDE_PANEL_OPEN_WINDOWS_KEY]: Array.from(openWindows) }).catch(() => {
36
+ });
37
+ };
38
+ var restoreOpenWindows = () => {
39
+ chrome.storage.session.get(SIDE_PANEL_OPEN_WINDOWS_KEY).then((data) => {
40
+ const stored = data[SIDE_PANEL_OPEN_WINDOWS_KEY];
41
+ if (Array.isArray(stored)) {
42
+ for (const id of stored) {
43
+ if (typeof id === "number") {
44
+ openWindows.add(id);
45
+ }
46
+ }
47
+ }
48
+ }).catch(() => {
49
+ });
50
+ };
51
+ var isSidePanelOpen = () => openWindows.size > 0;
52
+ var initSidePanelToggle = () => {
53
+ chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false }).catch(() => {
54
+ });
55
+ const canToggle = "onOpened" in chrome.sidePanel;
56
+ if (canToggle) {
57
+ restoreOpenWindows();
58
+ chrome.sidePanel.onOpened.addListener(({ windowId }) => {
59
+ openWindows.add(windowId);
60
+ persistOpenWindows();
61
+ });
62
+ chrome.sidePanel.onClosed.addListener(({ windowId }) => {
63
+ openWindows.delete(windowId);
64
+ persistOpenWindows();
65
+ });
66
+ chrome.windows.onRemoved.addListener((windowId) => {
67
+ openWindows.delete(windowId);
68
+ persistOpenWindows();
69
+ });
70
+ }
71
+ chrome.action.onClicked.addListener(({ windowId }) => {
72
+ void (async () => {
73
+ if (canToggle && openWindows.has(windowId)) {
74
+ try {
75
+ await chrome.windows.get(windowId);
76
+ } catch {
77
+ openWindows.delete(windowId);
78
+ persistOpenWindows();
79
+ await chrome.sidePanel.open({ windowId }).catch(() => {
80
+ });
81
+ return;
82
+ }
83
+ chrome.sidePanel.close({ windowId }).catch(() => {
84
+ });
85
+ } else {
86
+ chrome.sidePanel.open({ windowId }).catch(() => {
87
+ });
88
+ }
89
+ })();
90
+ });
91
+ };
92
+
1
93
  // dist/confirmation-badge.js
2
94
  var pendingConfirmationCount = 0;
95
+ var NOTIFICATION_ID = "opentabs-confirmation";
96
+ var pendingConfirmations = /* @__PURE__ */ new Map();
3
97
  var confirmationTimeouts = /* @__PURE__ */ new Map();
4
98
  var clearedConfirmationIds = /* @__PURE__ */ new Set();
5
99
  var CONFIRMATION_BACKGROUND_TIMEOUT_BUFFER_MS = 2e3;
@@ -15,11 +109,39 @@ var updateConfirmationBadge = () => {
15
109
  });
16
110
  }
17
111
  };
112
+ var syncConfirmationNotification = () => {
113
+ if (pendingConfirmationCount <= 0 || isSidePanelOpen()) {
114
+ chrome.notifications.clear(NOTIFICATION_ID).catch(() => {
115
+ });
116
+ return;
117
+ }
118
+ let message;
119
+ if (pendingConfirmationCount === 1 && pendingConfirmations.size === 1) {
120
+ const info = pendingConfirmations.values().next().value;
121
+ message = info.domain ? `${info.tool} on ${info.domain}` : info.tool;
122
+ } else if (pendingConfirmationCount > 1) {
123
+ message = `${pendingConfirmationCount} tools awaiting approval`;
124
+ } else {
125
+ message = "1 tool awaiting approval";
126
+ }
127
+ chrome.notifications.create(NOTIFICATION_ID, {
128
+ type: "basic",
129
+ iconUrl: chrome.runtime.getURL("icons/icon-128.png"),
130
+ title: "OpenTabs \u2014 Approval Required",
131
+ message,
132
+ priority: 2,
133
+ requireInteraction: true
134
+ }).catch(() => {
135
+ });
136
+ };
18
137
  var notifyConfirmationRequest = (params) => {
19
138
  const tool = typeof params.tool === "string" ? params.tool : "unknown tool";
20
- const domain = typeof params.domain === "string" ? params.domain : "unknown domain";
139
+ const domain = typeof params.domain === "string" ? params.domain : null;
21
140
  const id = typeof params.id === "string" ? params.id : String(Date.now());
141
+ const tabId = typeof params.tabId === "number" ? params.tabId : void 0;
142
+ const paramsPreview = typeof params.paramsPreview === "string" ? params.paramsPreview : "";
22
143
  const timeoutMs = typeof params.timeoutMs === "number" ? params.timeoutMs : 0;
144
+ const receivedAt = Date.now();
23
145
  const existingTimeoutId = confirmationTimeouts.get(id);
24
146
  if (existingTimeoutId !== void 0) {
25
147
  clearTimeout(existingTimeoutId);
@@ -28,6 +150,7 @@ var notifyConfirmationRequest = (params) => {
28
150
  pendingConfirmationCount++;
29
151
  updateConfirmationBadge();
30
152
  }
153
+ pendingConfirmations.set(id, { id, tool, domain, tabId, paramsPreview, timeoutMs, receivedAt });
31
154
  const effectiveTimeoutMs = timeoutMs > 0 ? timeoutMs : CONFIRMATION_FALLBACK_TIMEOUT_MS;
32
155
  const bgTimeoutId = setTimeout(() => {
33
156
  confirmationTimeouts.delete(id);
@@ -35,15 +158,7 @@ var notifyConfirmationRequest = (params) => {
35
158
  clearedConfirmationIds.delete(id);
36
159
  }, effectiveTimeoutMs + CONFIRMATION_BACKGROUND_TIMEOUT_BUFFER_MS);
37
160
  confirmationTimeouts.set(id, bgTimeoutId);
38
- chrome.notifications.create(`opentabs-confirm-${id}`, {
39
- type: "basic",
40
- iconUrl: chrome.runtime.getURL("icons/icon-128.png"),
41
- title: "OpenTabs: Approval Required",
42
- message: `${tool} on ${domain} \u2014 Click to open side panel`,
43
- priority: 2,
44
- requireInteraction: true
45
- }).catch(() => {
46
- });
161
+ syncConfirmationNotification();
47
162
  };
48
163
  var clearConfirmationBadge = (id) => {
49
164
  if (id !== void 0) {
@@ -51,11 +166,11 @@ var clearConfirmationBadge = (id) => {
51
166
  return;
52
167
  }
53
168
  clearedConfirmationIds.add(id);
54
- chrome.notifications.clear(`opentabs-confirm-${id}`).catch(() => {
55
- });
169
+ pendingConfirmations.delete(id);
56
170
  }
57
171
  pendingConfirmationCount = Math.max(0, pendingConfirmationCount - 1);
58
172
  updateConfirmationBadge();
173
+ syncConfirmationNotification();
59
174
  if (id !== void 0 && !confirmationTimeouts.has(id)) {
60
175
  clearedConfirmationIds.delete(id);
61
176
  }
@@ -69,19 +184,20 @@ var clearConfirmationBackgroundTimeout = (id) => {
69
184
  }
70
185
  };
71
186
  var clearAllConfirmationBadges = () => {
72
- for (const [id, timeoutId] of confirmationTimeouts.entries()) {
187
+ for (const [, timeoutId] of confirmationTimeouts.entries()) {
73
188
  clearTimeout(timeoutId);
74
- chrome.notifications.clear(`opentabs-confirm-${id}`).catch(() => {
75
- });
76
189
  }
77
190
  confirmationTimeouts.clear();
78
191
  clearedConfirmationIds.clear();
192
+ pendingConfirmations.clear();
79
193
  pendingConfirmationCount = 0;
80
194
  updateConfirmationBadge();
195
+ chrome.notifications.clear(NOTIFICATION_ID).catch(() => {
196
+ });
81
197
  };
82
198
  var initConfirmationBadge = () => {
83
199
  chrome.notifications.onClicked.addListener((notificationId) => {
84
- if (notificationId.startsWith("opentabs-confirm-")) {
200
+ if (notificationId === NOTIFICATION_ID) {
85
201
  chrome.windows.getCurrent().then((w) => {
86
202
  if (w.id !== void 0) {
87
203
  chrome.sidePanel.open({ windowId: w.id }).catch(() => {
@@ -94,36 +210,7 @@ var initConfirmationBadge = () => {
94
210
  }
95
211
  });
96
212
  };
97
-
98
- // dist/constants.js
99
- var KEEPALIVE_ALARM = "opentabs-keepalive";
100
- var KEEPALIVE_INTERVAL_MINUTES = 0.5;
101
- var PLUGINS_META_KEY = "plugins_meta";
102
- var WS_CONNECTED_KEY = "wsConnected";
103
- var SIDE_PANEL_OPEN_WINDOWS_KEY = "sidePanelOpenWindows";
104
- var SCRIPT_TIMEOUT_MS = 25e3;
105
- var MAX_SCRIPT_TIMEOUT_MS = 295e3;
106
- var IS_READY_TIMEOUT_MS = 5e3;
107
- var RELOAD_FLUSH_DELAY_MS = 100;
108
- var INJECTION_RETRY_DELAY_MS = 200;
109
- var SCREENSHOT_RENDER_DELAY_MS = 100;
110
- var WS_FLUSH_DELAY_MS = 50;
111
- var SERVER_PORT_KEY = "serverPort";
112
- var DEFAULT_SERVER_PORT = 9515;
113
- var TEXT_PREVIEW_MAX_LENGTH = 200;
114
- var DEFAULT_WAIT_TIMEOUT_MS = 1e4;
115
- var POLL_INTERVAL_MS = 100;
116
- var DEFAULT_QUERY_LIMIT = 100;
117
- var MAX_INPUT_SIZE = 10 * 1024 * 1024;
118
- var SIDE_PANEL_TIMEOUT_MS = 3e3;
119
- var CDP_VERSION = "1.3";
120
- var EXEC_MAX_ASYNC_WAIT_MS = 1e4;
121
- var EXEC_POLL_INTERVAL_MS = 50;
122
- var EXEC_RESULT_TRUNCATION_LIMIT = 5e4;
123
- var DEFAULT_LOG_LIMIT = 100;
124
- var VALID_PLUGIN_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
125
- var isValidPluginName = (name) => VALID_PLUGIN_NAME.test(name);
126
- var buildWsUrl = (port) => `ws://localhost:${port}/ws`;
213
+ var getPendingConfirmations = () => [...pendingConfirmations.values()];
127
214
 
128
215
  // dist/json-rpc-errors.js
129
216
  var JSONRPC_METHOD_NOT_FOUND = -32601;
@@ -1185,6 +1272,20 @@ var findAllMatchingTabs = async (plugin) => {
1185
1272
  // dist/tab-state.js
1186
1273
  var serializeTabState = (info) => JSON.stringify({ state: info.state, tabs: info.tabs });
1187
1274
  var lastKnownState = /* @__PURE__ */ new Map();
1275
+ var LAST_KNOWN_STATE_SESSION_KEY = "lastKnownState";
1276
+ var lastKnownStatePersistTimer;
1277
+ var persistLastKnownStateToSession = () => {
1278
+ chrome.storage.session.set({ [LAST_KNOWN_STATE_SESSION_KEY]: Object.fromEntries(lastKnownState) }).catch(() => {
1279
+ });
1280
+ };
1281
+ var scheduleLastKnownStatePersist = () => {
1282
+ if (lastKnownStatePersistTimer !== void 0)
1283
+ clearTimeout(lastKnownStatePersistTimer);
1284
+ lastKnownStatePersistTimer = setTimeout(() => {
1285
+ lastKnownStatePersistTimer = void 0;
1286
+ persistLastKnownStateToSession();
1287
+ }, 500);
1288
+ };
1188
1289
  var pluginLocks = /* @__PURE__ */ new Map();
1189
1290
  var withPluginLock = (pluginName, fn) => {
1190
1291
  const prev = pluginLocks.get(pluginName) ?? Promise.resolve();
@@ -1238,33 +1339,45 @@ var computePluginTabState = async (plugin) => {
1238
1339
  if (matchingTabs.length === 0) {
1239
1340
  return { state: "closed", tabs: [] };
1240
1341
  }
1241
- const tabInfos = [];
1242
- for (const tab of matchingTabs) {
1243
- if (tab.id === void 0)
1244
- continue;
1342
+ const validTabs = matchingTabs.filter((tab) => tab.id !== void 0);
1343
+ const results = await Promise.allSettled(validTabs.map(async (tab) => {
1245
1344
  let ready = false;
1246
1345
  try {
1247
1346
  ready = await probeTabReadiness(tab.id, plugin.name);
1248
1347
  } catch (err2) {
1249
1348
  console.warn(`[opentabs] computePluginTabState failed for plugin ${plugin.name} in tab ${tab.id}:`, err2);
1250
1349
  }
1251
- tabInfos.push({
1350
+ return {
1252
1351
  tabId: tab.id,
1253
1352
  url: tab.url ?? "",
1254
1353
  title: tab.title ?? "",
1255
1354
  ready
1256
- });
1355
+ };
1356
+ }));
1357
+ const tabInfos = [];
1358
+ for (const result of results) {
1359
+ if (result.status === "fulfilled") {
1360
+ tabInfos.push(result.value);
1361
+ }
1257
1362
  }
1258
1363
  const hasReady = tabInfos.some((t) => t.ready);
1259
1364
  const state = hasReady ? "ready" : "unavailable";
1260
1365
  return { state, tabs: tabInfos };
1261
1366
  };
1262
1367
  var sendTabSyncAll = async () => {
1368
+ const syncAllStart = Date.now();
1369
+ console.log("[opentabs:timing] sendTabSyncAll started");
1263
1370
  const index = await getAllPluginMeta();
1264
1371
  const plugins = Object.values(index);
1265
1372
  if (plugins.length === 0)
1266
1373
  return;
1267
- const settled = await Promise.allSettled(plugins.map(async (plugin) => [plugin.name, await computePluginTabState(plugin)]));
1374
+ const settled = await Promise.allSettled(plugins.map(async (plugin) => {
1375
+ const t0 = Date.now();
1376
+ const result = [plugin.name, await computePluginTabState(plugin)];
1377
+ console.log(`[opentabs:timing] computePluginTabState(${plugin.name}): ${Date.now() - t0}ms`);
1378
+ return result;
1379
+ }));
1380
+ console.log(`[opentabs:timing] all probes done: +${Date.now() - syncAllStart}ms`);
1268
1381
  const entries = [];
1269
1382
  for (const result of settled) {
1270
1383
  if (result.status === "fulfilled") {
@@ -1281,15 +1394,20 @@ var sendTabSyncAll = async () => {
1281
1394
  pluginNamesInSync.add(pluginName);
1282
1395
  return withPluginLock(pluginName, () => {
1283
1396
  lastKnownState.set(pluginName, serializeTabState(stateInfo));
1397
+ scheduleLastKnownStatePersist();
1284
1398
  return Promise.resolve();
1285
1399
  });
1286
1400
  }));
1401
+ let removedStale = false;
1287
1402
  for (const key of lastKnownState.keys()) {
1288
1403
  if (!pluginNamesInSync.has(key)) {
1289
1404
  lastKnownState.delete(key);
1290
1405
  pluginLocks.delete(key);
1406
+ removedStale = true;
1291
1407
  }
1292
1408
  }
1409
+ if (removedStale)
1410
+ scheduleLastKnownStatePersist();
1293
1411
  sendToServer({
1294
1412
  jsonrpc: "2.0",
1295
1413
  method: "tab.syncAll",
@@ -1306,23 +1424,56 @@ var sendTabSyncAll = async () => {
1306
1424
  });
1307
1425
  }
1308
1426
  };
1427
+ var flushLastKnownStateToSession = () => {
1428
+ if (lastKnownStatePersistTimer !== void 0) {
1429
+ clearTimeout(lastKnownStatePersistTimer);
1430
+ lastKnownStatePersistTimer = void 0;
1431
+ }
1432
+ persistLastKnownStateToSession();
1433
+ };
1309
1434
  var clearTabStateCache = () => {
1310
1435
  lastKnownState.clear();
1311
1436
  pluginLocks.clear();
1437
+ if (lastKnownStatePersistTimer !== void 0) {
1438
+ clearTimeout(lastKnownStatePersistTimer);
1439
+ lastKnownStatePersistTimer = void 0;
1440
+ }
1441
+ chrome.storage.session.remove(LAST_KNOWN_STATE_SESSION_KEY).catch(() => {
1442
+ });
1312
1443
  };
1313
1444
  var clearPluginTabState = (pluginName) => {
1445
+ const had = lastKnownState.has(pluginName);
1314
1446
  lastKnownState.delete(pluginName);
1315
1447
  pluginLocks.delete(pluginName);
1448
+ if (had)
1449
+ scheduleLastKnownStatePersist();
1316
1450
  };
1317
1451
  var updateLastKnownState = (pluginName, stateInfo) => withPluginLock(pluginName, () => {
1318
1452
  lastKnownState.set(pluginName, serializeTabState(stateInfo));
1453
+ scheduleLastKnownStatePersist();
1319
1454
  return Promise.resolve();
1320
1455
  });
1321
1456
  var getLastKnownStates = () => lastKnownState;
1457
+ var loadLastKnownStateFromSession = async () => {
1458
+ lastKnownState.clear();
1459
+ try {
1460
+ const data = await chrome.storage.session.get(LAST_KNOWN_STATE_SESSION_KEY);
1461
+ const stored = data[LAST_KNOWN_STATE_SESSION_KEY];
1462
+ if (stored && typeof stored === "object" && !Array.isArray(stored)) {
1463
+ for (const [key, value] of Object.entries(stored)) {
1464
+ if (typeof value === "string") {
1465
+ lastKnownState.set(key, value);
1466
+ }
1467
+ }
1468
+ }
1469
+ } catch {
1470
+ }
1471
+ };
1322
1472
  var getAggregateState = (serialized) => {
1323
1473
  try {
1324
1474
  const parsed = JSON.parse(serialized);
1325
- return parsed.state;
1475
+ const validStates = /* @__PURE__ */ new Set(["closed", "unavailable", "ready"]);
1476
+ return validStates.has(parsed.state) ? parsed.state : "closed";
1326
1477
  } catch {
1327
1478
  return "closed";
1328
1479
  }
@@ -1335,6 +1486,7 @@ var notifyAffectedPlugins = async (affectedPlugins) => {
1335
1486
  if (previous === serialized)
1336
1487
  return;
1337
1488
  lastKnownState.set(plugin.name, serialized);
1489
+ scheduleLastKnownStatePersist();
1338
1490
  sendTabStateNotification(plugin.name, newState);
1339
1491
  })));
1340
1492
  };
@@ -1382,6 +1534,46 @@ var checkTabChanged = async (changedTabId, changeInfo) => {
1382
1534
  return;
1383
1535
  await notifyAffectedPlugins(affectedPlugins);
1384
1536
  };
1537
+ var readinessPollTimer;
1538
+ var readinessPollRunning = false;
1539
+ var runReadinessPoll = async () => {
1540
+ if (readinessPollRunning)
1541
+ return;
1542
+ readinessPollRunning = true;
1543
+ try {
1544
+ const index = await getAllPluginMeta();
1545
+ const plugins = Object.values(index);
1546
+ if (plugins.length === 0)
1547
+ return;
1548
+ const activePlugins = plugins.filter((p) => {
1549
+ const cached = lastKnownState.get(p.name);
1550
+ return cached !== void 0 && getAggregateState(cached) !== "closed";
1551
+ });
1552
+ if (activePlugins.length === 0)
1553
+ return;
1554
+ await notifyAffectedPlugins(activePlugins);
1555
+ } catch (err2) {
1556
+ console.warn("[opentabs] readiness poll failed:", err2);
1557
+ } finally {
1558
+ readinessPollRunning = false;
1559
+ }
1560
+ };
1561
+ var startReadinessPoll = () => {
1562
+ if (readinessPollTimer !== void 0)
1563
+ return;
1564
+ readinessPollTimer = setInterval(() => {
1565
+ runReadinessPoll().catch((err2) => {
1566
+ console.warn("[opentabs] readiness poll tick failed:", err2);
1567
+ });
1568
+ }, READINESS_POLL_INTERVAL_MS);
1569
+ };
1570
+ var stopReadinessPoll = () => {
1571
+ if (readinessPollTimer !== void 0) {
1572
+ clearInterval(readinessPollTimer);
1573
+ readinessPollTimer = void 0;
1574
+ }
1575
+ readinessPollRunning = false;
1576
+ };
1385
1577
 
1386
1578
  // dist/browser-commands/extension-commands.js
1387
1579
  var handleExtensionGetState = async (id) => {
@@ -1450,7 +1642,8 @@ var handleExtensionGetLogs = async (params, id) => {
1450
1642
  if (typeof params.since === "number") {
1451
1643
  filterOptions.since = params.since;
1452
1644
  }
1453
- const bgEntries = bgLogCollector.getEntries(filterOptions);
1645
+ const { limit: _, ...sourceOptions } = filterOptions;
1646
+ const bgEntries = bgLogCollector.getEntries(sourceOptions);
1454
1647
  const bgStats = bgLogCollector.getStats();
1455
1648
  let offscreenEntries = [];
1456
1649
  let offscreenStats = {
@@ -1462,7 +1655,7 @@ var handleExtensionGetLogs = async (params, id) => {
1462
1655
  try {
1463
1656
  const raw = await chrome.runtime.sendMessage({
1464
1657
  type: "offscreen:getLogs",
1465
- options: Object.keys(filterOptions).length > 0 ? filterOptions : void 0
1658
+ options: Object.keys(sourceOptions).length > 0 ? sourceOptions : void 0
1466
1659
  });
1467
1660
  const response = raw;
1468
1661
  if (response && Array.isArray(response.entries)) {
@@ -2222,6 +2415,42 @@ var handleBrowserHandleDialog = async (params, id) => {
2222
2415
  };
2223
2416
 
2224
2417
  // dist/browser-commands/key-press-command.js
2418
+ var SHIFTED_PUNCTUATION_CODES = {
2419
+ "!": "Digit1",
2420
+ "@": "Digit2",
2421
+ "#": "Digit3",
2422
+ $: "Digit4",
2423
+ "%": "Digit5",
2424
+ "^": "Digit6",
2425
+ "&": "Digit7",
2426
+ "*": "Digit8",
2427
+ "(": "Digit9",
2428
+ ")": "Digit0",
2429
+ _: "Minus",
2430
+ "+": "Equal",
2431
+ "{": "BracketLeft",
2432
+ "}": "BracketRight",
2433
+ "|": "Backslash",
2434
+ ":": "Semicolon",
2435
+ '"': "Quote",
2436
+ "<": "Comma",
2437
+ ">": "Period",
2438
+ "?": "Slash",
2439
+ "~": "Backquote"
2440
+ };
2441
+ var UNSHIFTED_PUNCTUATION_CODES = {
2442
+ "-": "Minus",
2443
+ "=": "Equal",
2444
+ "[": "BracketLeft",
2445
+ "]": "BracketRight",
2446
+ "\\": "Backslash",
2447
+ ";": "Semicolon",
2448
+ "'": "Quote",
2449
+ ",": "Comma",
2450
+ ".": "Period",
2451
+ "/": "Slash",
2452
+ "`": "Backquote"
2453
+ };
2225
2454
  var handleBrowserPressKey = async (params, id) => {
2226
2455
  try {
2227
2456
  const tabId = requireTabId(params, id);
@@ -2239,7 +2468,7 @@ var handleBrowserPressKey = async (params, id) => {
2239
2468
  const results = await chrome.scripting.executeScript({
2240
2469
  target: { tabId },
2241
2470
  world: "MAIN",
2242
- func: (k, sel, shift, ctrl, alt, meta) => {
2471
+ func: (k, sel, shift, ctrl, alt, meta, shiftedPunct, unshiftedPunct) => {
2243
2472
  let target = null;
2244
2473
  if (sel) {
2245
2474
  target = document.querySelector(sel);
@@ -2258,7 +2487,7 @@ var handleBrowserPressKey = async (params, id) => {
2258
2487
  return `Digit${k2}`;
2259
2488
  if (k2 === " ")
2260
2489
  return "Space";
2261
- return k2;
2490
+ return shiftedPunct[k2] ?? unshiftedPunct[k2] ?? k2;
2262
2491
  }
2263
2492
  return k2;
2264
2493
  };
@@ -2343,7 +2572,7 @@ var handleBrowserPressKey = async (params, id) => {
2343
2572
  }
2344
2573
  };
2345
2574
  },
2346
- args: [key, selector, shiftKey, ctrlKey, altKey, metaKey]
2575
+ args: [key, selector, shiftKey, ctrlKey, altKey, metaKey, SHIFTED_PUNCTUATION_CODES, UNSHIFTED_PUNCTUATION_CODES]
2347
2576
  });
2348
2577
  const result = extractScriptResult(results, id);
2349
2578
  if (!result)
@@ -2954,6 +3183,208 @@ var checkRateLimit = (method, now = Date.now()) => {
2954
3183
  return true;
2955
3184
  };
2956
3185
 
3186
+ // dist/server-request.js
3187
+ var REQUEST_TIMEOUT_MS = 3e4;
3188
+ var pendingRequests = /* @__PURE__ */ new Map();
3189
+ var nextId = 1;
3190
+ var sendServerRequest = (method, params = {}) => {
3191
+ const id = nextId++;
3192
+ const data = { jsonrpc: "2.0", method, params, id };
3193
+ return new Promise((resolve, reject) => {
3194
+ const timerId = setTimeout(() => {
3195
+ if (pendingRequests.has(id)) {
3196
+ pendingRequests.delete(id);
3197
+ reject(new Error(`Request ${method} timed out after ${REQUEST_TIMEOUT_MS}ms`));
3198
+ }
3199
+ }, REQUEST_TIMEOUT_MS);
3200
+ pendingRequests.set(id, { resolve, reject, timerId });
3201
+ sendToServer(data);
3202
+ });
3203
+ };
3204
+ var consumeServerResponse = (data) => {
3205
+ if (data.method !== void 0)
3206
+ return false;
3207
+ const rawId = data.id;
3208
+ if (rawId === void 0 || rawId === null)
3209
+ return false;
3210
+ const id = typeof rawId === "number" ? rawId : void 0;
3211
+ if (id === void 0)
3212
+ return false;
3213
+ const pending = pendingRequests.get(id);
3214
+ if (!pending)
3215
+ return false;
3216
+ pendingRequests.delete(id);
3217
+ clearTimeout(pending.timerId);
3218
+ if (data.error !== void 0 && data.error !== null) {
3219
+ const err2 = data.error;
3220
+ pending.reject(new Error(err2.message ?? "Unknown server error"));
3221
+ } else {
3222
+ pending.resolve(data.result);
3223
+ }
3224
+ return true;
3225
+ };
3226
+ var rejectAllPendingServerRequests = () => {
3227
+ for (const [id, pending] of pendingRequests) {
3228
+ pendingRequests.delete(id);
3229
+ clearTimeout(pending.timerId);
3230
+ pending.reject(new Error("Server disconnected"));
3231
+ }
3232
+ };
3233
+
3234
+ // dist/server-state-cache.js
3235
+ var SESSION_KEY = "serverStateCache";
3236
+ var CACHES_INITIALIZED_KEY = "cachesInitialized";
3237
+ var EMPTY_CACHE = {
3238
+ plugins: [],
3239
+ failedPlugins: [],
3240
+ browserTools: [],
3241
+ serverVersion: void 0
3242
+ };
3243
+ var cache = { ...EMPTY_CACHE };
3244
+ var pendingPluginToolUpdates = /* @__PURE__ */ new Map();
3245
+ var pendingBrowserToolUpdates = /* @__PURE__ */ new Map();
3246
+ var reapplyPendingOptimisticUpdates = () => {
3247
+ if (pendingPluginToolUpdates.size > 0) {
3248
+ cache = {
3249
+ ...cache,
3250
+ plugins: cache.plugins.map((plugin) => {
3251
+ const toolOverrides = pendingPluginToolUpdates.get(plugin.name);
3252
+ if (!toolOverrides)
3253
+ return plugin;
3254
+ return {
3255
+ ...plugin,
3256
+ tools: plugin.tools.map((tool) => {
3257
+ const override = toolOverrides.get(tool.name);
3258
+ return override !== void 0 ? { ...tool, enabled: override } : tool;
3259
+ })
3260
+ };
3261
+ })
3262
+ };
3263
+ }
3264
+ if (pendingBrowserToolUpdates.size > 0) {
3265
+ cache = {
3266
+ ...cache,
3267
+ browserTools: cache.browserTools.map((bt) => {
3268
+ const override = pendingBrowserToolUpdates.get(bt.name);
3269
+ return override !== void 0 ? { ...bt, enabled: override } : bt;
3270
+ })
3271
+ };
3272
+ }
3273
+ };
3274
+ var addPendingPluginToolUpdate = (plugin, tool, enabled) => {
3275
+ let toolMap = pendingPluginToolUpdates.get(plugin);
3276
+ if (!toolMap) {
3277
+ toolMap = /* @__PURE__ */ new Map();
3278
+ pendingPluginToolUpdates.set(plugin, toolMap);
3279
+ }
3280
+ toolMap.set(tool, enabled);
3281
+ };
3282
+ var removePendingPluginToolUpdate = (plugin, tool) => {
3283
+ const toolMap = pendingPluginToolUpdates.get(plugin);
3284
+ if (!toolMap)
3285
+ return;
3286
+ toolMap.delete(tool);
3287
+ if (toolMap.size === 0)
3288
+ pendingPluginToolUpdates.delete(plugin);
3289
+ };
3290
+ var addPendingPluginAllToolsUpdate = (plugin, toolNames, enabled) => {
3291
+ let toolMap = pendingPluginToolUpdates.get(plugin);
3292
+ if (!toolMap) {
3293
+ toolMap = /* @__PURE__ */ new Map();
3294
+ pendingPluginToolUpdates.set(plugin, toolMap);
3295
+ }
3296
+ for (const name of toolNames) {
3297
+ toolMap.set(name, enabled);
3298
+ }
3299
+ };
3300
+ var removePendingPluginAllToolsUpdate = (plugin, toolNames) => {
3301
+ const toolMap = pendingPluginToolUpdates.get(plugin);
3302
+ if (!toolMap)
3303
+ return;
3304
+ for (const name of toolNames) {
3305
+ toolMap.delete(name);
3306
+ }
3307
+ if (toolMap.size === 0)
3308
+ pendingPluginToolUpdates.delete(plugin);
3309
+ };
3310
+ var addPendingBrowserToolUpdate = (tool, enabled) => {
3311
+ pendingBrowserToolUpdates.set(tool, enabled);
3312
+ };
3313
+ var removePendingBrowserToolUpdate = (tool) => {
3314
+ pendingBrowserToolUpdates.delete(tool);
3315
+ };
3316
+ var addPendingAllBrowserToolsUpdate = (toolNames, enabled) => {
3317
+ for (const name of toolNames) {
3318
+ pendingBrowserToolUpdates.set(name, enabled);
3319
+ }
3320
+ };
3321
+ var removePendingAllBrowserToolsUpdate = (toolNames) => {
3322
+ for (const name of toolNames) {
3323
+ pendingBrowserToolUpdates.delete(name);
3324
+ }
3325
+ };
3326
+ var cachesInitialized = false;
3327
+ var persistTimer;
3328
+ var persistToSession = () => {
3329
+ chrome.storage.session.set({ [SESSION_KEY]: cache, [CACHES_INITIALIZED_KEY]: cachesInitialized }).catch(() => {
3330
+ });
3331
+ };
3332
+ var schedulePersist = () => {
3333
+ if (persistTimer !== void 0)
3334
+ clearTimeout(persistTimer);
3335
+ persistTimer = setTimeout(() => {
3336
+ persistTimer = void 0;
3337
+ persistToSession();
3338
+ }, 500);
3339
+ };
3340
+ var getServerStateCache = () => structuredClone(cache);
3341
+ var updateServerStateCache = (partial) => {
3342
+ cache = { ...cache, ...partial };
3343
+ reapplyPendingOptimisticUpdates();
3344
+ schedulePersist();
3345
+ };
3346
+ var flushServerStateCacheToSession = () => {
3347
+ if (persistTimer !== void 0) {
3348
+ clearTimeout(persistTimer);
3349
+ persistTimer = void 0;
3350
+ }
3351
+ persistToSession();
3352
+ };
3353
+ var clearServerStateCache = () => {
3354
+ cache = { ...EMPTY_CACHE };
3355
+ cachesInitialized = false;
3356
+ pendingPluginToolUpdates.clear();
3357
+ pendingBrowserToolUpdates.clear();
3358
+ if (persistTimer !== void 0) {
3359
+ clearTimeout(persistTimer);
3360
+ persistTimer = void 0;
3361
+ }
3362
+ chrome.storage.session.remove([SESSION_KEY, CACHES_INITIALIZED_KEY]).catch(() => {
3363
+ });
3364
+ };
3365
+ var loadServerStateCacheFromSession = async () => {
3366
+ try {
3367
+ const data = await chrome.storage.session.get([SESSION_KEY, CACHES_INITIALIZED_KEY]);
3368
+ const stored = data[SESSION_KEY];
3369
+ if (stored && typeof stored === "object") {
3370
+ cache = {
3371
+ plugins: Array.isArray(stored.plugins) ? stored.plugins : [],
3372
+ failedPlugins: Array.isArray(stored.failedPlugins) ? stored.failedPlugins : [],
3373
+ browserTools: Array.isArray(stored.browserTools) ? stored.browserTools : [],
3374
+ serverVersion: typeof stored.serverVersion === "string" ? stored.serverVersion : void 0
3375
+ };
3376
+ }
3377
+ if (typeof data[CACHES_INITIALIZED_KEY] === "boolean") {
3378
+ cachesInitialized = data[CACHES_INITIALIZED_KEY];
3379
+ }
3380
+ } catch {
3381
+ }
3382
+ };
3383
+ var getCachesInitialized = () => cachesInitialized;
3384
+ var setCachesInitialized = (value) => {
3385
+ cachesInitialized = value;
3386
+ };
3387
+
2957
3388
  // dist/dispatch-helpers.js
2958
3389
  var isAdapterNotReady = (result) => result.type === "error" && result.code === JSONRPC_ADAPTER_NOT_READY;
2959
3390
  var resolvePlugin = async (pluginName, id) => {
@@ -2968,26 +3399,6 @@ var resolvePlugin = async (pluginName, id) => {
2968
3399
  }
2969
3400
  return plugin;
2970
3401
  };
2971
- var executeWithTimeout = async (scriptPromise, timeoutMs, fallbackMessage) => {
2972
- let timeoutId;
2973
- const timeoutPromise = new Promise((_resolve, reject) => {
2974
- timeoutId = setTimeout(() => {
2975
- reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
2976
- }, timeoutMs);
2977
- });
2978
- let results;
2979
- try {
2980
- results = await Promise.race([scriptPromise, timeoutPromise]);
2981
- } finally {
2982
- clearTimeout(timeoutId);
2983
- }
2984
- const firstResult = results[0];
2985
- const result = firstResult?.result;
2986
- if (!result || typeof result !== "object" || !("type" in result)) {
2987
- return { type: "error", code: JSONRPC_INTERNAL_ERROR, message: fallbackMessage };
2988
- }
2989
- return result;
2990
- };
2991
3402
  var dispatchWithTabFallback = async (config) => {
2992
3403
  const { id, pluginName, plugin, operationName, executeOnTab } = config;
2993
3404
  const matchingTabs = await findAllMatchingTabs(plugin);
@@ -3113,202 +3524,6 @@ var dispatchToTargetedTab = async (config) => {
3113
3524
  }
3114
3525
  };
3115
3526
 
3116
- // dist/resource-prompt-dispatch.js
3117
- var executeResourceReadOnTab = async (tabId, pluginName, resourceUri) => {
3118
- const scriptPromise = chrome.scripting.executeScript({
3119
- target: { tabId },
3120
- world: "MAIN",
3121
- func: async (pName, uri) => {
3122
- const ot = globalThis.__openTabs;
3123
- const adapter = ot?.adapters?.[pName];
3124
- if (!adapter || typeof adapter !== "object") {
3125
- return { type: "error", code: -32002, message: `Adapter "${pName}" not injected or not ready` };
3126
- }
3127
- if (!Object.isFrozen(adapter)) {
3128
- return {
3129
- type: "error",
3130
- code: -32002,
3131
- message: `Adapter "${pName}" failed integrity check (not frozen)`
3132
- };
3133
- }
3134
- if (typeof adapter.isReady !== "function") {
3135
- return { type: "error", code: -32002, message: `Adapter "${pName}" has no isReady function` };
3136
- }
3137
- let ready;
3138
- try {
3139
- ready = await adapter.isReady();
3140
- } catch {
3141
- return { type: "error", code: -32002, message: `Adapter "${pName}" isReady() threw an error` };
3142
- }
3143
- if (!ready) {
3144
- return {
3145
- type: "error",
3146
- code: -32002,
3147
- message: `Plugin "${pName}" is not ready (state: unavailable)`
3148
- };
3149
- }
3150
- if (!Array.isArray(adapter.resources)) {
3151
- return { type: "error", code: -32603, message: `Adapter "${pName}" has no resources array` };
3152
- }
3153
- const resource = adapter.resources.find((r) => r.uri === uri);
3154
- if (!resource || typeof resource.read !== "function") {
3155
- return { type: "error", code: -32603, message: `Resource "${uri}" not found in adapter "${pName}"` };
3156
- }
3157
- try {
3158
- const output = await resource.read(uri);
3159
- return { type: "success", output };
3160
- } catch (err2) {
3161
- const caughtError = err2;
3162
- return {
3163
- type: "error",
3164
- code: -32603,
3165
- message: caughtError.message ?? "Resource read failed"
3166
- };
3167
- }
3168
- },
3169
- args: [pluginName, resourceUri]
3170
- });
3171
- return executeWithTimeout(scriptPromise, SCRIPT_TIMEOUT_MS, "No result from resource read");
3172
- };
3173
- var executePromptGetOnTab = async (tabId, pluginName, promptName, promptArgs) => {
3174
- const scriptPromise = chrome.scripting.executeScript({
3175
- target: { tabId },
3176
- world: "MAIN",
3177
- func: async (pName, pPromptName, pArgs) => {
3178
- const ot = globalThis.__openTabs;
3179
- const adapter = ot?.adapters?.[pName];
3180
- if (!adapter || typeof adapter !== "object") {
3181
- return { type: "error", code: -32002, message: `Adapter "${pName}" not injected or not ready` };
3182
- }
3183
- if (!Object.isFrozen(adapter)) {
3184
- return {
3185
- type: "error",
3186
- code: -32002,
3187
- message: `Adapter "${pName}" failed integrity check (not frozen)`
3188
- };
3189
- }
3190
- if (typeof adapter.isReady !== "function") {
3191
- return { type: "error", code: -32002, message: `Adapter "${pName}" has no isReady function` };
3192
- }
3193
- let ready;
3194
- try {
3195
- ready = await adapter.isReady();
3196
- } catch {
3197
- return { type: "error", code: -32002, message: `Adapter "${pName}" isReady() threw an error` };
3198
- }
3199
- if (!ready) {
3200
- return {
3201
- type: "error",
3202
- code: -32002,
3203
- message: `Plugin "${pName}" is not ready (state: unavailable)`
3204
- };
3205
- }
3206
- if (!Array.isArray(adapter.prompts)) {
3207
- return { type: "error", code: -32603, message: `Adapter "${pName}" has no prompts array` };
3208
- }
3209
- const prompt = adapter.prompts.find((p) => p.name === pPromptName);
3210
- if (!prompt || typeof prompt.render !== "function") {
3211
- return {
3212
- type: "error",
3213
- code: -32603,
3214
- message: `Prompt "${pPromptName}" not found in adapter "${pName}"`
3215
- };
3216
- }
3217
- try {
3218
- const output = await prompt.render(pArgs);
3219
- return { type: "success", output };
3220
- } catch (err2) {
3221
- const caughtError = err2;
3222
- return {
3223
- type: "error",
3224
- code: -32603,
3225
- message: caughtError.message ?? "Prompt render failed"
3226
- };
3227
- }
3228
- },
3229
- args: [pluginName, promptName, promptArgs]
3230
- });
3231
- return executeWithTimeout(scriptPromise, SCRIPT_TIMEOUT_MS, "No result from prompt render");
3232
- };
3233
- var handleResourceRead = async (params, id) => {
3234
- const pluginName = requireStringParam(params, "plugin", id);
3235
- if (!pluginName)
3236
- return;
3237
- const resourceUri = requireStringParam(params, "uri", id);
3238
- if (!resourceUri)
3239
- return;
3240
- const rawTabId = params.tabId;
3241
- const targetTabId = typeof rawTabId === "number" && Number.isInteger(rawTabId) && rawTabId > 0 ? rawTabId : void 0;
3242
- const plugin = await resolvePlugin(pluginName, id);
3243
- if (!plugin)
3244
- return;
3245
- const executeOnTab = (tid) => executeResourceReadOnTab(tid, pluginName, resourceUri);
3246
- if (targetTabId !== void 0) {
3247
- await dispatchToTargetedTab({
3248
- id,
3249
- pluginName,
3250
- plugin,
3251
- tabId: targetTabId,
3252
- operationName: "resource read",
3253
- executeOnTab
3254
- });
3255
- } else {
3256
- await dispatchWithTabFallback({
3257
- id,
3258
- pluginName,
3259
- plugin,
3260
- operationName: "resource read",
3261
- executeOnTab
3262
- });
3263
- }
3264
- };
3265
- var handlePromptGet = async (params, id) => {
3266
- const pluginName = requireStringParam(params, "plugin", id);
3267
- if (!pluginName)
3268
- return;
3269
- const promptName = requireStringParam(params, "prompt", id);
3270
- if (!promptName)
3271
- return;
3272
- const rawArgs = params.arguments;
3273
- if (rawArgs !== void 0 && rawArgs !== null && (typeof rawArgs !== "object" || Array.isArray(rawArgs))) {
3274
- sendToServer({
3275
- jsonrpc: "2.0",
3276
- error: { code: JSONRPC_INVALID_PARAMS, message: 'Invalid "arguments" param (expected object)' },
3277
- id
3278
- });
3279
- return;
3280
- }
3281
- const rawObj = rawArgs ?? {};
3282
- const promptArgs = {};
3283
- for (const [key, val] of Object.entries(rawObj)) {
3284
- promptArgs[key] = String(val);
3285
- }
3286
- const rawTabId = params.tabId;
3287
- const targetTabId = typeof rawTabId === "number" && Number.isInteger(rawTabId) && rawTabId > 0 ? rawTabId : void 0;
3288
- const plugin = await resolvePlugin(pluginName, id);
3289
- if (!plugin)
3290
- return;
3291
- const executeOnTab = (tid) => executePromptGetOnTab(tid, pluginName, promptName, promptArgs);
3292
- if (targetTabId !== void 0) {
3293
- await dispatchToTargetedTab({
3294
- id,
3295
- pluginName,
3296
- plugin,
3297
- tabId: targetTabId,
3298
- operationName: "prompt get",
3299
- executeOnTab
3300
- });
3301
- } else {
3302
- await dispatchWithTabFallback({
3303
- id,
3304
- pluginName,
3305
- plugin,
3306
- operationName: "prompt get",
3307
- executeOnTab
3308
- });
3309
- }
3310
- };
3311
-
3312
3527
  // dist/tool-dispatch.js
3313
3528
  var progressCallbacks = /* @__PURE__ */ new Map();
3314
3529
  var notifyDispatchProgress = (dispatchId) => {
@@ -3505,7 +3720,7 @@ var executeToolOnTab = async (tabId, pluginName, toolName, input, dispatchId) =>
3505
3720
  return result;
3506
3721
  };
3507
3722
  var handleToolDispatch = async (params, id) => {
3508
- const dispatchId = typeof params.dispatchId === "string" ? params.dispatchId : String(id);
3723
+ const dispatchId = typeof params.__opentabs_dispatchId === "string" ? params.__opentabs_dispatchId : String(id);
3509
3724
  const pluginName = requireStringParam(params, "plugin", id);
3510
3725
  if (!pluginName)
3511
3726
  return;
@@ -3601,7 +3816,6 @@ var wrapNotification = (method, fn) => (params) => {
3601
3816
  fn(params).catch((err2) => console.warn(`[opentabs] ${method} handler failed:`, err2));
3602
3817
  };
3603
3818
  var SIDE_PANEL_METHODS = /* @__PURE__ */ new Set([
3604
- "tab.stateChanged",
3605
3819
  "tool.invocationStart",
3606
3820
  "tool.invocationEnd",
3607
3821
  "plugins.changed",
@@ -3635,13 +3849,18 @@ var validatePluginPayload = (raw) => {
3635
3849
  return null;
3636
3850
  }
3637
3851
  const urlPatterns = Array.isArray(obj.urlPatterns) ? obj.urlPatterns.filter((p) => typeof p === "string") : [];
3638
- const tools = Array.isArray(obj.tools) ? obj.tools.filter((t) => typeof t === "object" && t !== null && typeof t.name === "string" && typeof t.description === "string" && typeof t.enabled === "boolean").map((t) => ({
3639
- name: t.name,
3640
- displayName: typeof t.displayName === "string" ? t.displayName : t.name,
3641
- description: t.description,
3642
- icon: typeof t.icon === "string" ? t.icon : "wrench",
3643
- enabled: t.enabled
3644
- })) : [];
3852
+ const tools = Array.isArray(obj.tools) ? obj.tools.filter((t) => typeof t === "object" && t !== null && typeof t.name === "string" && typeof t.description === "string").map((t) => {
3853
+ if (typeof t.enabled !== "boolean") {
3854
+ console.warn(`[opentabs] Tool "${t.name}" in plugin "${obj.name}" is missing the "enabled" field \u2014 defaulting to enabled=true. This is a server-side bug.`);
3855
+ }
3856
+ return {
3857
+ name: t.name,
3858
+ displayName: typeof t.displayName === "string" ? t.displayName : t.name,
3859
+ description: t.description,
3860
+ icon: typeof t.icon === "string" ? t.icon : "wrench",
3861
+ enabled: typeof t.enabled === "boolean" ? t.enabled : true
3862
+ };
3863
+ }) : [];
3645
3864
  return {
3646
3865
  name: obj.name,
3647
3866
  version: typeof obj.version === "string" ? obj.version : "0.0.0",
@@ -3668,6 +3887,8 @@ var handleExtensionReload = (_params, id) => {
3668
3887
  });
3669
3888
  };
3670
3889
  var handleSyncFull = async (params) => {
3890
+ const syncStart = Date.now();
3891
+ console.log("[opentabs:timing] handleSyncFull started");
3671
3892
  const rawPlugins = params.plugins;
3672
3893
  if (!Array.isArray(rawPlugins))
3673
3894
  return;
@@ -3701,17 +3922,58 @@ var handleSyncFull = async (params) => {
3701
3922
  }
3702
3923
  const metas = uniquePlugins.map(toPluginMeta);
3703
3924
  await storePluginsBatch(metas);
3925
+ console.log(`[opentabs:timing] storePluginsBatch done: +${Date.now() - syncStart}ms`);
3704
3926
  const injectionResults = await Promise.allSettled(metas.map((meta) => injectPluginIntoMatchingTabs(meta.name, meta.urlPatterns, true, meta.adapterHash, meta.adapterFile, meta.adapterHash)));
3705
3927
  for (const result of injectionResults) {
3706
3928
  if (result.status === "rejected") {
3707
3929
  console.warn("[opentabs] Plugin injection failed during sync.full:", result.reason);
3708
3930
  }
3709
3931
  }
3710
- await sendTabSyncAll();
3932
+ console.log(`[opentabs:timing] injection done: +${Date.now() - syncStart}ms`);
3933
+ const rawPluginMap = /* @__PURE__ */ new Map();
3934
+ for (const raw of rawPlugins) {
3935
+ if (typeof raw === "object" && raw !== null && typeof raw.name === "string") {
3936
+ rawPluginMap.set(raw.name, raw);
3937
+ }
3938
+ }
3939
+ const cachePlugins = uniquePlugins.map((p) => {
3940
+ const raw = rawPluginMap.get(p.name);
3941
+ return {
3942
+ name: p.name,
3943
+ displayName: p.displayName,
3944
+ version: p.version,
3945
+ trustTier: p.trustTier,
3946
+ source: raw?.source === "npm" || raw?.source === "local" ? raw.source : "local",
3947
+ tabState: "closed",
3948
+ urlPatterns: p.urlPatterns,
3949
+ tools: p.tools,
3950
+ iconSvg: p.iconSvg,
3951
+ iconInactiveSvg: p.iconInactiveSvg,
3952
+ ...typeof raw?.sdkVersion === "string" ? { sdkVersion: raw.sdkVersion } : {},
3953
+ ...raw?.update && typeof raw.update === "object" ? { update: raw.update } : {}
3954
+ };
3955
+ });
3956
+ const rawFailedPlugins = Array.isArray(params.failedPlugins) ? params.failedPlugins : void 0;
3957
+ const rawBrowserTools = Array.isArray(params.browserTools) ? params.browserTools : void 0;
3958
+ const rawServerVersion = typeof params.serverVersion === "string" ? params.serverVersion : void 0;
3959
+ updateServerStateCache({
3960
+ plugins: cachePlugins,
3961
+ ...rawFailedPlugins ? { failedPlugins: rawFailedPlugins } : {},
3962
+ ...rawBrowserTools ? { browserTools: rawBrowserTools } : {},
3963
+ ...rawServerVersion !== void 0 ? { serverVersion: rawServerVersion } : {}
3964
+ });
3965
+ setCachesInitialized(true);
3966
+ flushServerStateCacheToSession();
3967
+ console.log(`[opentabs:timing] cache + flush done: +${Date.now() - syncStart}ms`);
3711
3968
  forwardToSidePanel({
3712
3969
  type: "sp:serverMessage",
3713
3970
  data: { jsonrpc: "2.0", method: "plugins.changed" }
3714
3971
  });
3972
+ console.log(`[opentabs:timing] plugins.changed sent to side panel: +${Date.now() - syncStart}ms`);
3973
+ void sendTabSyncAll().then(() => {
3974
+ flushLastKnownStateToSession();
3975
+ startReadinessPoll();
3976
+ });
3715
3977
  };
3716
3978
  var handlePluginUpdate = async (params) => {
3717
3979
  const validated = validatePluginPayload(params);
@@ -3723,6 +3985,23 @@ var handlePluginUpdate = async (params) => {
3723
3985
  const newState = await computePluginTabState(meta);
3724
3986
  await updateLastKnownState(meta.name, newState);
3725
3987
  sendTabStateNotification(meta.name, newState);
3988
+ const existingCache = getServerStateCache();
3989
+ const updatedPlugin = {
3990
+ name: validated.name,
3991
+ displayName: validated.displayName,
3992
+ version: validated.version,
3993
+ trustTier: validated.trustTier,
3994
+ source: params.source === "npm" || params.source === "local" ? params.source : "local",
3995
+ tabState: newState.state,
3996
+ urlPatterns: validated.urlPatterns,
3997
+ tools: validated.tools,
3998
+ iconSvg: validated.iconSvg,
3999
+ iconInactiveSvg: validated.iconInactiveSvg,
4000
+ ...typeof params.sdkVersion === "string" ? { sdkVersion: params.sdkVersion } : {},
4001
+ ...params.update && typeof params.update === "object" ? { update: params.update } : {}
4002
+ };
4003
+ const otherPlugins = existingCache.plugins.filter((p) => p.name !== validated.name);
4004
+ updateServerStateCache({ plugins: [...otherPlugins, updatedPlugin] });
3726
4005
  forwardToSidePanel({
3727
4006
  type: "sp:serverMessage",
3728
4007
  data: { jsonrpc: "2.0", method: "plugins.changed" }
@@ -3757,20 +4036,40 @@ var handlePluginUninstall = async (params, id) => {
3757
4036
  }
3758
4037
  await removePlugin(pluginName);
3759
4038
  clearPluginTabState(pluginName);
4039
+ const existingCache = getServerStateCache();
4040
+ updateServerStateCache({ plugins: existingCache.plugins.filter((p) => p.name !== pluginName) });
4041
+ forwardToSidePanel({
4042
+ type: "sp:serverMessage",
4043
+ data: { jsonrpc: "2.0", method: "plugins.changed" }
4044
+ });
3760
4045
  sendToServer({
3761
4046
  jsonrpc: "2.0",
3762
4047
  result: { success: true },
3763
4048
  id
3764
4049
  });
3765
4050
  };
4051
+ var handleExtensionGetTabState = async (_params, id) => {
4052
+ let states = getLastKnownStates();
4053
+ if (states.size === 0) {
4054
+ await loadLastKnownStateFromSession();
4055
+ states = getLastKnownStates();
4056
+ }
4057
+ const tabStates = {};
4058
+ for (const [pluginName, serialized] of states) {
4059
+ try {
4060
+ tabStates[pluginName] = JSON.parse(serialized);
4061
+ } catch {
4062
+ tabStates[pluginName] = { state: "closed", tabs: [] };
4063
+ }
4064
+ }
4065
+ sendToServer({ jsonrpc: "2.0", result: { tabStates }, id });
4066
+ };
3766
4067
  var methodHandlers = /* @__PURE__ */ new Map([
3767
4068
  ["extension.reload", handleExtensionReload],
3768
4069
  ["sync.full", wrapNotification("sync.full", handleSyncFull)],
3769
4070
  ["plugin.update", wrapNotification("plugin.update", handlePluginUpdate)],
3770
4071
  ["plugin.uninstall", wrapAsync("plugin.uninstall", handlePluginUninstall)],
3771
4072
  ["tool.dispatch", wrapAsync("tool.dispatch", handleToolDispatch)],
3772
- ["resource.read", wrapAsync("resource.read", handleResourceRead)],
3773
- ["prompt.get", wrapAsync("prompt.get", handlePromptGet)],
3774
4073
  ["browser.listTabs", wrapAsync("browser.listTabs", (_params, id) => handleBrowserListTabs(id))],
3775
4074
  ["browser.openTab", wrapAsync("browser.openTab", handleBrowserOpenTab)],
3776
4075
  ["browser.closeTab", wrapAsync("browser.closeTab", handleBrowserCloseTab)],
@@ -3809,14 +4108,26 @@ var methodHandlers = /* @__PURE__ */ new Map([
3809
4108
  [
3810
4109
  "extension.forceReconnect",
3811
4110
  wrapAsync("extension.forceReconnect", (_params, id) => handleExtensionForceReconnect(id))
3812
- ]
4111
+ ],
4112
+ ["extension.getTabState", wrapAsync("extension.getTabState", handleExtensionGetTabState)]
3813
4113
  ]);
3814
4114
  var handleServerMessage = (message) => {
3815
4115
  const method = message.method;
3816
4116
  const id = message.id;
3817
4117
  const params = message.params ?? {};
3818
- const isResponse = id !== void 0 && !method;
3819
- if (isResponse || method && SIDE_PANEL_METHODS.has(method)) {
4118
+ if (!method && consumeServerResponse(message)) {
4119
+ return;
4120
+ }
4121
+ if (method === "plugins.changed") {
4122
+ const payload = params;
4123
+ updateServerStateCache({
4124
+ ...payload.plugins ? { plugins: payload.plugins } : {},
4125
+ ...payload.failedPlugins ? { failedPlugins: payload.failedPlugins } : {},
4126
+ ...payload.browserTools ? { browserTools: payload.browserTools } : {},
4127
+ ...payload.serverVersion !== void 0 ? { serverVersion: payload.serverVersion } : {}
4128
+ });
4129
+ }
4130
+ if (method && SIDE_PANEL_METHODS.has(method)) {
3820
4131
  forwardToSidePanel({ type: "sp:serverMessage", data: message });
3821
4132
  }
3822
4133
  if (method === "confirmation.request") {
@@ -3853,14 +4164,18 @@ var methodHandlerNames = Array.from(methodHandlers.keys());
3853
4164
  // dist/background-message-handlers.js
3854
4165
  var wsConnected = false;
3855
4166
  var lastDisconnectReason;
4167
+ var wsConnectedRestorePromise;
3856
4168
  var restoreWsConnectedState = () => {
3857
- chrome.storage.session.get(WS_CONNECTED_KEY).then((data) => {
4169
+ if (wsConnectedRestorePromise !== void 0)
4170
+ return;
4171
+ wsConnectedRestorePromise = chrome.storage.session.get(WS_CONNECTED_KEY).then((data) => {
3858
4172
  if (typeof data[WS_CONNECTED_KEY] === "boolean") {
3859
4173
  wsConnected = data[WS_CONNECTED_KEY];
3860
4174
  }
3861
4175
  }).catch(() => {
3862
4176
  });
3863
4177
  };
4178
+ var waitForWsConnectedRestore = () => wsConnectedRestorePromise ?? Promise.resolve();
3864
4179
  var persistWsConnected = (connected) => {
3865
4180
  wsConnected = connected;
3866
4181
  chrome.storage.session.set({ [WS_CONNECTED_KEY]: connected }).catch(() => {
@@ -3888,23 +4203,92 @@ var handleWsState = (message, sendResponse) => {
3888
4203
  }
3889
4204
  });
3890
4205
  if (!nowConnected) {
4206
+ stopReadinessPoll();
3891
4207
  clearTabStateCache();
4208
+ clearServerStateCache();
4209
+ rejectAllPendingServerRequests();
3892
4210
  clearAllConfirmationBadges();
3893
4211
  }
3894
4212
  sendResponse({ ok: true });
3895
4213
  };
3896
4214
  var handleWsMessage = (message, sendResponse) => {
3897
- handleServerMessage(message.data);
3898
- sendResponse({ ok: true });
3899
- };
3900
- var handleBgSend = (message, sendResponse) => {
3901
- sendToServer(message.data);
4215
+ try {
4216
+ handleServerMessage(message.data);
4217
+ } catch (err2) {
4218
+ console.error("[opentabs:background] handleServerMessage threw:", err2);
4219
+ }
3902
4220
  sendResponse({ ok: true });
3903
4221
  };
3904
- var handleBgGetConnectionState = (_message, sendResponse) => {
3905
- sendResponse({
3906
- connected: wsConnected,
3907
- disconnectReason: wsConnected ? void 0 : lastDisconnectReason
4222
+ var handleBgGetFullState = (_message, sendResponse) => {
4223
+ (async () => {
4224
+ await waitForWsConnectedRestore();
4225
+ let tabStates = getLastKnownStates();
4226
+ let serverCache = getServerStateCache();
4227
+ if (wsConnected && tabStates.size === 0 && serverCache.plugins.length === 0) {
4228
+ await loadServerStateCacheFromSession();
4229
+ if (getCachesInitialized()) {
4230
+ await loadLastKnownStateFromSession();
4231
+ tabStates = getLastKnownStates();
4232
+ }
4233
+ serverCache = getServerStateCache();
4234
+ }
4235
+ const metaIndex = await getAllPluginMeta();
4236
+ const serverPluginMap = /* @__PURE__ */ new Map();
4237
+ for (const sp of serverCache.plugins) {
4238
+ serverPluginMap.set(sp.name, sp);
4239
+ }
4240
+ const plugins = Object.values(metaIndex).map((meta) => {
4241
+ const serverPlugin = serverPluginMap.get(meta.name);
4242
+ let tabState = "closed";
4243
+ const serialized = tabStates.get(meta.name);
4244
+ if (serialized) {
4245
+ try {
4246
+ const parsed = JSON.parse(serialized);
4247
+ tabState = parsed.state;
4248
+ } catch {
4249
+ }
4250
+ }
4251
+ const tools = meta.tools.map((metaTool) => {
4252
+ const serverTool = serverPlugin?.tools.find((st) => st.name === metaTool.name);
4253
+ return {
4254
+ ...metaTool,
4255
+ enabled: serverTool?.enabled ?? true
4256
+ };
4257
+ });
4258
+ return {
4259
+ name: meta.name,
4260
+ displayName: meta.displayName,
4261
+ version: meta.version,
4262
+ trustTier: meta.trustTier,
4263
+ urlPatterns: meta.urlPatterns,
4264
+ iconSvg: meta.iconSvg,
4265
+ iconInactiveSvg: meta.iconInactiveSvg,
4266
+ tools,
4267
+ tabState,
4268
+ source: serverPlugin?.source ?? "local",
4269
+ sdkVersion: serverPlugin?.sdkVersion,
4270
+ update: serverPlugin?.update
4271
+ };
4272
+ });
4273
+ sendResponse({
4274
+ connected: wsConnected,
4275
+ disconnectReason: wsConnected ? void 0 : lastDisconnectReason,
4276
+ plugins,
4277
+ failedPlugins: serverCache.failedPlugins,
4278
+ browserTools: serverCache.browserTools,
4279
+ serverVersion: serverCache.serverVersion,
4280
+ pendingConfirmations: getPendingConfirmations()
4281
+ });
4282
+ })().catch(() => {
4283
+ sendResponse({
4284
+ connected: wsConnected,
4285
+ disconnectReason: wsConnected ? void 0 : lastDisconnectReason,
4286
+ plugins: [],
4287
+ failedPlugins: [],
4288
+ browserTools: [],
4289
+ serverVersion: void 0,
4290
+ pendingConfirmations: []
4291
+ });
3908
4292
  });
3909
4293
  };
3910
4294
  var handlePluginLogs = (message, sendResponse) => {
@@ -3975,6 +4359,160 @@ var handleSpConfirmationTimeout = (message, sendResponse) => {
3975
4359
  clearConfirmationBadge(id);
3976
4360
  sendResponse({ ok: true });
3977
4361
  };
4362
+ var handleBgSetToolEnabled = (message, sendResponse) => {
4363
+ const plugin = message.plugin;
4364
+ const tool = message.tool;
4365
+ const enabled = message.enabled;
4366
+ const cache2 = getServerStateCache();
4367
+ const pluginEntry = cache2.plugins.find((p) => p.name === plugin);
4368
+ const originalEnabled = pluginEntry?.tools.find((t) => t.name === tool)?.enabled ?? !enabled;
4369
+ const updatedPlugins = cache2.plugins.map((p) => {
4370
+ if (p.name !== plugin)
4371
+ return p;
4372
+ return {
4373
+ ...p,
4374
+ tools: p.tools.map((t) => t.name === tool ? { ...t, enabled } : t)
4375
+ };
4376
+ });
4377
+ addPendingPluginToolUpdate(plugin, tool, enabled);
4378
+ updateServerStateCache({ plugins: updatedPlugins });
4379
+ sendServerRequest("config.setToolEnabled", { plugin, tool, enabled }).then((result) => {
4380
+ removePendingPluginToolUpdate(plugin, tool);
4381
+ sendResponse(result);
4382
+ }).catch((err2) => {
4383
+ removePendingPluginToolUpdate(plugin, tool);
4384
+ const currentCache = getServerStateCache();
4385
+ const revertedPlugins = currentCache.plugins.map((p) => {
4386
+ if (p.name !== plugin)
4387
+ return p;
4388
+ return {
4389
+ ...p,
4390
+ tools: p.tools.map((t) => t.name === tool ? { ...t, enabled: originalEnabled } : t)
4391
+ };
4392
+ });
4393
+ updateServerStateCache({ plugins: revertedPlugins });
4394
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4395
+ });
4396
+ };
4397
+ var handleBgSetAllToolsEnabled = (message, sendResponse) => {
4398
+ const plugin = message.plugin;
4399
+ const enabled = message.enabled;
4400
+ const cache2 = getServerStateCache();
4401
+ const pluginEntry = cache2.plugins.find((p) => p.name === plugin);
4402
+ const toolNames = pluginEntry ? pluginEntry.tools.map((t) => t.name) : [];
4403
+ const originalToolStates = /* @__PURE__ */ new Map();
4404
+ if (pluginEntry) {
4405
+ for (const t of pluginEntry.tools) {
4406
+ originalToolStates.set(t.name, t.enabled);
4407
+ }
4408
+ }
4409
+ const updatedPlugins = cache2.plugins.map((p) => {
4410
+ if (p.name !== plugin)
4411
+ return p;
4412
+ return {
4413
+ ...p,
4414
+ tools: p.tools.map((t) => ({ ...t, enabled }))
4415
+ };
4416
+ });
4417
+ addPendingPluginAllToolsUpdate(plugin, toolNames, enabled);
4418
+ updateServerStateCache({ plugins: updatedPlugins });
4419
+ sendServerRequest("config.setAllToolsEnabled", { plugin, enabled }).then((result) => {
4420
+ removePendingPluginAllToolsUpdate(plugin, toolNames);
4421
+ sendResponse(result);
4422
+ }).catch((err2) => {
4423
+ removePendingPluginAllToolsUpdate(plugin, toolNames);
4424
+ const currentCache = getServerStateCache();
4425
+ const revertedPlugins = currentCache.plugins.map((p) => {
4426
+ if (p.name !== plugin)
4427
+ return p;
4428
+ return {
4429
+ ...p,
4430
+ tools: p.tools.map((t) => ({
4431
+ ...t,
4432
+ enabled: originalToolStates.get(t.name) ?? t.enabled
4433
+ }))
4434
+ };
4435
+ });
4436
+ updateServerStateCache({ plugins: revertedPlugins });
4437
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4438
+ });
4439
+ };
4440
+ var handleBgSetBrowserToolEnabled = (message, sendResponse) => {
4441
+ const tool = message.tool;
4442
+ const enabled = message.enabled;
4443
+ const cache2 = getServerStateCache();
4444
+ const originalEnabled = cache2.browserTools.find((bt) => bt.name === tool)?.enabled ?? !enabled;
4445
+ const updatedBrowserTools = cache2.browserTools.map((bt) => bt.name === tool ? { ...bt, enabled } : bt);
4446
+ addPendingBrowserToolUpdate(tool, enabled);
4447
+ updateServerStateCache({ browserTools: updatedBrowserTools });
4448
+ sendServerRequest("config.setBrowserToolEnabled", { tool, enabled }).then((result) => {
4449
+ removePendingBrowserToolUpdate(tool);
4450
+ sendResponse(result);
4451
+ }).catch((err2) => {
4452
+ removePendingBrowserToolUpdate(tool);
4453
+ const currentCache = getServerStateCache();
4454
+ const revertedBrowserTools = currentCache.browserTools.map((bt) => bt.name === tool ? { ...bt, enabled: originalEnabled } : bt);
4455
+ updateServerStateCache({ browserTools: revertedBrowserTools });
4456
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4457
+ });
4458
+ };
4459
+ var handleBgSetAllBrowserToolsEnabled = (message, sendResponse) => {
4460
+ const enabled = message.enabled;
4461
+ const cache2 = getServerStateCache();
4462
+ const toolNames = cache2.browserTools.map((bt) => bt.name);
4463
+ const originalToolStates = /* @__PURE__ */ new Map();
4464
+ for (const bt of cache2.browserTools) {
4465
+ originalToolStates.set(bt.name, bt.enabled);
4466
+ }
4467
+ const updatedBrowserTools = cache2.browserTools.map((bt) => ({ ...bt, enabled }));
4468
+ addPendingAllBrowserToolsUpdate(toolNames, enabled);
4469
+ updateServerStateCache({ browserTools: updatedBrowserTools });
4470
+ sendServerRequest("config.setAllBrowserToolsEnabled", { enabled }).then((result) => {
4471
+ removePendingAllBrowserToolsUpdate(toolNames);
4472
+ sendResponse(result);
4473
+ }).catch((err2) => {
4474
+ removePendingAllBrowserToolsUpdate(toolNames);
4475
+ const currentCache = getServerStateCache();
4476
+ const revertedBrowserTools = currentCache.browserTools.map((bt) => ({
4477
+ ...bt,
4478
+ enabled: originalToolStates.get(bt.name) ?? bt.enabled
4479
+ }));
4480
+ updateServerStateCache({ browserTools: revertedBrowserTools });
4481
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4482
+ });
4483
+ };
4484
+ var handleBgSearchPlugins = (message, sendResponse) => {
4485
+ const query = message.query;
4486
+ sendServerRequest("plugin.search", { query }).then((result) => {
4487
+ sendResponse(result);
4488
+ }).catch((err2) => {
4489
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4490
+ });
4491
+ };
4492
+ var handleBgInstallPlugin = (message, sendResponse) => {
4493
+ const name = message.name;
4494
+ sendServerRequest("plugin.install", { name }).then((result) => {
4495
+ sendResponse(result);
4496
+ }).catch((err2) => {
4497
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4498
+ });
4499
+ };
4500
+ var handleBgRemovePlugin = (message, sendResponse) => {
4501
+ const name = message.name;
4502
+ sendServerRequest("plugin.remove", { name }).then((result) => {
4503
+ sendResponse(result);
4504
+ }).catch((err2) => {
4505
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4506
+ });
4507
+ };
4508
+ var handleBgUpdatePlugin = (message, sendResponse) => {
4509
+ const name = message.name;
4510
+ sendServerRequest("plugin.updateFromRegistry", { name }).then((result) => {
4511
+ sendResponse(result);
4512
+ }).catch((err2) => {
4513
+ sendResponse({ error: err2 instanceof Error ? err2.message : String(err2) });
4514
+ });
4515
+ };
3978
4516
  var handlePortChanged = (message, sendResponse) => {
3979
4517
  chrome.runtime.sendMessage(message).catch(() => {
3980
4518
  });
@@ -3984,8 +4522,15 @@ var backgroundHandlers = /* @__PURE__ */ new Map([
3984
4522
  ["offscreen:getUrl", handleOffscreenGetUrl],
3985
4523
  ["ws:state", handleWsState],
3986
4524
  ["ws:message", handleWsMessage],
3987
- ["bg:send", handleBgSend],
3988
- ["bg:getConnectionState", handleBgGetConnectionState],
4525
+ ["bg:getFullState", handleBgGetFullState],
4526
+ ["bg:setToolEnabled", handleBgSetToolEnabled],
4527
+ ["bg:setAllToolsEnabled", handleBgSetAllToolsEnabled],
4528
+ ["bg:setBrowserToolEnabled", handleBgSetBrowserToolEnabled],
4529
+ ["bg:setAllBrowserToolsEnabled", handleBgSetAllBrowserToolsEnabled],
4530
+ ["bg:searchPlugins", handleBgSearchPlugins],
4531
+ ["bg:installPlugin", handleBgInstallPlugin],
4532
+ ["bg:removePlugin", handleBgRemovePlugin],
4533
+ ["bg:updatePlugin", handleBgUpdatePlugin],
3989
4534
  ["plugin:logs", handlePluginLogs],
3990
4535
  ["tool:progress", handleToolProgress],
3991
4536
  ["sp:confirmationResponse", handleSpConfirmationResponse],
@@ -3996,8 +4541,15 @@ var EXTENSION_ONLY_TYPES = /* @__PURE__ */ new Set([
3996
4541
  "offscreen:getUrl",
3997
4542
  "ws:state",
3998
4543
  "ws:message",
3999
- "bg:send",
4000
- "bg:getConnectionState",
4544
+ "bg:getFullState",
4545
+ "bg:setToolEnabled",
4546
+ "bg:setAllToolsEnabled",
4547
+ "bg:setBrowserToolEnabled",
4548
+ "bg:setAllBrowserToolsEnabled",
4549
+ "bg:searchPlugins",
4550
+ "bg:installPlugin",
4551
+ "bg:removePlugin",
4552
+ "bg:updatePlugin",
4001
4553
  "offscreen:getLogs",
4002
4554
  "sp:confirmationResponse",
4003
4555
  "sp:confirmationTimeout",
@@ -4019,66 +4571,6 @@ var initBackgroundMessageHandlers = () => {
4019
4571
  };
4020
4572
  var backgroundHandlerNames = [...backgroundHandlers.keys()];
4021
4573
 
4022
- // dist/side-panel-toggle.js
4023
- var openWindows = /* @__PURE__ */ new Set();
4024
- var persistOpenWindows = () => {
4025
- chrome.storage.session.set({ [SIDE_PANEL_OPEN_WINDOWS_KEY]: Array.from(openWindows) }).catch(() => {
4026
- });
4027
- };
4028
- var restoreOpenWindows = () => {
4029
- chrome.storage.session.get(SIDE_PANEL_OPEN_WINDOWS_KEY).then((data) => {
4030
- const stored = data[SIDE_PANEL_OPEN_WINDOWS_KEY];
4031
- if (Array.isArray(stored)) {
4032
- for (const id of stored) {
4033
- if (typeof id === "number") {
4034
- openWindows.add(id);
4035
- }
4036
- }
4037
- }
4038
- }).catch(() => {
4039
- });
4040
- };
4041
- var initSidePanelToggle = () => {
4042
- chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false }).catch(() => {
4043
- });
4044
- const canToggle = "onOpened" in chrome.sidePanel;
4045
- if (canToggle) {
4046
- restoreOpenWindows();
4047
- chrome.sidePanel.onOpened.addListener(({ windowId }) => {
4048
- openWindows.add(windowId);
4049
- persistOpenWindows();
4050
- });
4051
- chrome.sidePanel.onClosed.addListener(({ windowId }) => {
4052
- openWindows.delete(windowId);
4053
- persistOpenWindows();
4054
- });
4055
- chrome.windows.onRemoved.addListener((windowId) => {
4056
- openWindows.delete(windowId);
4057
- persistOpenWindows();
4058
- });
4059
- }
4060
- chrome.action.onClicked.addListener(({ windowId }) => {
4061
- void (async () => {
4062
- if (canToggle && openWindows.has(windowId)) {
4063
- try {
4064
- await chrome.windows.get(windowId);
4065
- } catch {
4066
- openWindows.delete(windowId);
4067
- persistOpenWindows();
4068
- await chrome.sidePanel.open({ windowId }).catch(() => {
4069
- });
4070
- return;
4071
- }
4072
- chrome.sidePanel.close({ windowId }).catch(() => {
4073
- });
4074
- } else {
4075
- chrome.sidePanel.open({ windowId }).catch(() => {
4076
- });
4077
- }
4078
- })();
4079
- });
4080
- };
4081
-
4082
4574
  // dist/background.js
4083
4575
  initSidePanelToggle();
4084
4576
  restoreWsConnectedState();
@@ -4108,7 +4600,9 @@ var setupKeepaliveAlarm = async () => {
4108
4600
  }
4109
4601
  };
4110
4602
  chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
4111
- if (changeInfo.status === "complete" && tab.url) {
4603
+ if (changeInfo.status === "loading" && tab.url) {
4604
+ injectPluginsIntoTab(tabId, tab.url).catch((err2) => console.warn("[opentabs] early tab injection failed:", err2));
4605
+ } else if (changeInfo.status === "complete" && tab.url) {
4112
4606
  injectPluginsIntoTab(tabId, tab.url).then(() => checkTabChanged(tabId, changeInfo)).catch((err2) => console.warn("[opentabs] tab injection failed:", err2));
4113
4607
  } else if (changeInfo.url) {
4114
4608
  checkTabChanged(tabId, changeInfo).catch((err2) => console.warn("[opentabs] tab state check failed:", err2));