@browserbridge/bbx 1.0.0 → 1.0.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/README.md +3 -1
- package/docs/api-reference.md +33 -33
- package/docs/mcp-vs-cli.md +104 -104
- package/docs/publishing.md +1 -3
- package/docs/quickstart.md +6 -6
- package/docs/unpacked-extension.md +72 -0
- package/manifest.json +3 -17
- package/package.json +44 -42
- package/packages/agent-client/src/cli-helpers.js +10 -5
- package/packages/agent-client/src/cli.js +65 -135
- package/packages/agent-client/src/client.js +37 -17
- package/packages/agent-client/src/command-registry.js +101 -69
- package/packages/agent-client/src/detect.js +3 -6
- package/packages/agent-client/src/install.js +10 -27
- package/packages/agent-client/src/mcp-config.js +11 -30
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +13 -28
- package/packages/extension/src/background-helpers.js +51 -36
- package/packages/extension/src/background-routing.js +11 -13
- package/packages/extension/src/background.js +562 -299
- package/packages/extension/src/content-script-helpers.js +17 -16
- package/packages/extension/src/content-script.js +175 -109
- package/packages/extension/src/sidepanel-helpers.js +3 -1
- package/packages/extension/ui/popup.js +39 -20
- package/packages/extension/ui/sidepanel.js +108 -191
- package/packages/extension/ui/ui.css +2 -1
- package/packages/mcp-server/src/handlers.js +546 -250
- package/packages/mcp-server/src/server.js +558 -257
- package/packages/native-host/bin/bridge-daemon.js +6 -2
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +11 -7
- package/packages/native-host/src/daemon.js +143 -92
- package/packages/native-host/src/install-manifest.js +73 -22
- package/packages/native-host/src/native-host.js +55 -40
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +3 -3
- package/packages/protocol/src/errors.js +11 -11
- package/packages/protocol/src/protocol.js +104 -71
- package/packages/protocol/src/registry.js +300 -45
- package/packages/protocol/src/summary.js +249 -106
- package/packages/protocol/src/types.js +1 -1
- package/skills/browser-bridge/SKILL.md +1 -1
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +33 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +125 -70
- package/skills/browser-bridge/references/tailwind.md +12 -11
- package/skills/browser-bridge/references/token-efficiency.md +23 -22
- package/skills/browser-bridge/references/ui-workflows.md +8 -0
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
|
@@ -148,57 +148,32 @@ const SETUP_MATRIX_ORDER = /** @type {const} */ ([
|
|
|
148
148
|
'agents',
|
|
149
149
|
]);
|
|
150
150
|
/** @type {Map<string, number>} */
|
|
151
|
-
const SETUP_MATRIX_RANK = new Map(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
'
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
);
|
|
163
|
-
const toggleButton = /** @type {HTMLButtonElement} */ (
|
|
164
|
-
document.getElementById('bridge-toggle')
|
|
165
|
-
);
|
|
166
|
-
const actionLog = /** @type {HTMLDivElement} */ (
|
|
167
|
-
document.getElementById('action-log')
|
|
168
|
-
);
|
|
169
|
-
const setupSection = /** @type {HTMLElement} */ (
|
|
170
|
-
document.getElementById('native-setup')
|
|
171
|
-
);
|
|
172
|
-
const setupInstallCmd = /** @type {HTMLElement} */ (
|
|
173
|
-
document.getElementById('setup-install-cmd')
|
|
174
|
-
);
|
|
175
|
-
const setupSkillCmd = /** @type {HTMLElement} */ (
|
|
176
|
-
document.getElementById('setup-skill-cmd')
|
|
177
|
-
);
|
|
178
|
-
const setupMcpCmd = /** @type {HTMLElement} */ (
|
|
179
|
-
document.getElementById('setup-mcp-cmd')
|
|
180
|
-
);
|
|
181
|
-
const controlSection = /** @type {HTMLElement} */ (
|
|
182
|
-
document.getElementById('control-section')
|
|
183
|
-
);
|
|
151
|
+
const SETUP_MATRIX_RANK = new Map(SETUP_MATRIX_ORDER.map((key, index) => [key, index]));
|
|
152
|
+
const SETUP_MATRIX_BETA_KEYS = new Set(['antigravity', 'windsurf', 'agents']);
|
|
153
|
+
|
|
154
|
+
const nativeIndicator =
|
|
155
|
+
/** @type {HTMLSpanElement} */ (document.getElementById('native-indicator'));
|
|
156
|
+
const toggleButton = /** @type {HTMLButtonElement} */ (document.getElementById('bridge-toggle'));
|
|
157
|
+
const actionLog = /** @type {HTMLDivElement} */ (document.getElementById('action-log'));
|
|
158
|
+
const setupSection = /** @type {HTMLElement} */ (document.getElementById('native-setup'));
|
|
159
|
+
const setupInstallCmd = /** @type {HTMLElement} */ (document.getElementById('setup-install-cmd'));
|
|
160
|
+
const setupSkillCmd = /** @type {HTMLElement} */ (document.getElementById('setup-skill-cmd'));
|
|
161
|
+
const setupMcpCmd = /** @type {HTMLElement} */ (document.getElementById('setup-mcp-cmd'));
|
|
162
|
+
const controlSection = /** @type {HTMLElement} */ (document.getElementById('control-section'));
|
|
184
163
|
const installationSection = /** @type {HTMLDetailsElement} */ (
|
|
185
164
|
document.getElementById('installation-section')
|
|
186
165
|
);
|
|
187
|
-
const setupStatusNote =
|
|
188
|
-
document.getElementById('setup-status-note')
|
|
189
|
-
);
|
|
166
|
+
const setupStatusNote =
|
|
167
|
+
/** @type {HTMLParagraphElement} */ (document.getElementById('setup-status-note'));
|
|
190
168
|
const setupStatusSummaryNote = /** @type {HTMLSpanElement} */ (
|
|
191
169
|
document.getElementById('setup-status-summary-note')
|
|
192
170
|
);
|
|
193
171
|
const setupStatusMatrix = /** @type {HTMLDivElement} */ (
|
|
194
172
|
document.getElementById('setup-status-matrix')
|
|
195
173
|
);
|
|
196
|
-
const activitySection = /** @type {HTMLElement} */ (
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
const activityHistogram = /** @type {HTMLDivElement} */ (
|
|
200
|
-
document.getElementById('activity-histogram')
|
|
201
|
-
);
|
|
174
|
+
const activitySection = /** @type {HTMLElement} */ (document.getElementById('activity-section'));
|
|
175
|
+
const activityHistogram =
|
|
176
|
+
/** @type {HTMLDivElement} */ (document.getElementById('activity-histogram'));
|
|
202
177
|
const activityHistogramBars = /** @type {HTMLDivElement} */ (
|
|
203
178
|
document.getElementById('activity-histogram-bars')
|
|
204
179
|
);
|
|
@@ -208,21 +183,15 @@ const activityHistogramRange = /** @type {HTMLSpanElement} */ (
|
|
|
208
183
|
const activitySummaryTokens = /** @type {HTMLSpanElement} */ (
|
|
209
184
|
document.getElementById('activity-summary-tokens')
|
|
210
185
|
);
|
|
211
|
-
const agentStatus = /** @type {HTMLDivElement} */ (
|
|
212
|
-
document.getElementById('agent-status')
|
|
213
|
-
);
|
|
186
|
+
const agentStatus = /** @type {HTMLDivElement} */ (document.getElementById('agent-status'));
|
|
214
187
|
const agentStatusDetail = /** @type {HTMLParagraphElement} */ (
|
|
215
188
|
document.getElementById('agent-status-detail')
|
|
216
189
|
);
|
|
217
|
-
const agentDisclosure =
|
|
218
|
-
document.getElementById('agent-disclosure')
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
);
|
|
223
|
-
const examplesContent = /** @type {HTMLDivElement} */ (
|
|
224
|
-
document.getElementById('examples-content')
|
|
225
|
-
);
|
|
190
|
+
const agentDisclosure =
|
|
191
|
+
/** @type {HTMLParagraphElement} */ (document.getElementById('agent-disclosure'));
|
|
192
|
+
const examplesSection =
|
|
193
|
+
/** @type {HTMLDetailsElement} */ (document.getElementById('examples-section'));
|
|
194
|
+
const examplesContent = /** @type {HTMLDivElement} */ (document.getElementById('examples-content'));
|
|
226
195
|
/** @type {SidePanelCurrentTab | null} */
|
|
227
196
|
let currentTabState = null;
|
|
228
197
|
/** @type {ActionLogEntry[]} */
|
|
@@ -265,7 +234,7 @@ const MCP_PROMPT_EXAMPLES = Object.freeze([
|
|
|
265
234
|
const ACTIVITY_HISTOGRAM_WINDOW_MS = 10 * 60 * 1000;
|
|
266
235
|
const ACTIVITY_HISTOGRAM_BUCKET_MS = 30 * 1000;
|
|
267
236
|
const ACTIVITY_HISTOGRAM_BARS = Math.floor(
|
|
268
|
-
ACTIVITY_HISTOGRAM_WINDOW_MS / ACTIVITY_HISTOGRAM_BUCKET_MS
|
|
237
|
+
ACTIVITY_HISTOGRAM_WINDOW_MS / ACTIVITY_HISTOGRAM_BUCKET_MS
|
|
269
238
|
);
|
|
270
239
|
const ACTIVITY_HISTOGRAM_TICK_MS = 5 * 1000;
|
|
271
240
|
const HISTOGRAM_METHOD_FAMILIES = /** @type {const} */ ([
|
|
@@ -303,8 +272,7 @@ function copySetupText(target, text) {
|
|
|
303
272
|
.then(() => {
|
|
304
273
|
target.classList.add('copied');
|
|
305
274
|
const copyButton = target.querySelector('.example-copy-button');
|
|
306
|
-
const resetLabel =
|
|
307
|
-
copyButton instanceof HTMLButtonElement ? copyButton.textContent : null;
|
|
275
|
+
const resetLabel = copyButton instanceof HTMLButtonElement ? copyButton.textContent : null;
|
|
308
276
|
if (copyButton instanceof HTMLButtonElement) {
|
|
309
277
|
copyButton.textContent = '✓';
|
|
310
278
|
}
|
|
@@ -350,38 +318,16 @@ function readRequestedTabId() {
|
|
|
350
318
|
/** @type {number | null} */
|
|
351
319
|
const requestedTabId = readRequestedTabId();
|
|
352
320
|
|
|
353
|
-
/**
|
|
354
|
-
* @returns {Promise<number | null>}
|
|
355
|
-
*/
|
|
356
|
-
async function resolveInitialScopeTabId() {
|
|
357
|
-
if (requestedTabId != null) {
|
|
358
|
-
return requestedTabId;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
363
|
-
return typeof activeTab?.id === 'number' ? activeTab.id : null;
|
|
364
|
-
} catch {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
321
|
/**
|
|
370
322
|
* @returns {Promise<void>}
|
|
371
323
|
*/
|
|
372
324
|
async function connectSidepanelPort() {
|
|
373
|
-
const initialScopeTabId = await resolveInitialScopeTabId();
|
|
374
325
|
const nextPort = chrome.runtime.connect({ name: 'ui-sidepanel' });
|
|
375
326
|
nextPort.onMessage.addListener(handlePortMessage);
|
|
376
327
|
nextPort.onDisconnect.addListener(() => {
|
|
377
328
|
setTimeout(connectSidepanelPort, 500);
|
|
378
329
|
});
|
|
379
|
-
nextPort.postMessage({
|
|
380
|
-
type: 'state.request',
|
|
381
|
-
scopeTabId: initialScopeTabId != null && initialScopeTabId > 0
|
|
382
|
-
? initialScopeTabId
|
|
383
|
-
: undefined,
|
|
384
|
-
});
|
|
330
|
+
nextPort.postMessage({ type: 'state.request' });
|
|
385
331
|
port = nextPort;
|
|
386
332
|
}
|
|
387
333
|
|
|
@@ -414,9 +360,6 @@ toggleButton.addEventListener('click', () => {
|
|
|
414
360
|
|
|
415
361
|
port.postMessage({
|
|
416
362
|
type: 'scope.set_enabled',
|
|
417
|
-
tabId: requestedTabId != null && requestedTabId > 0
|
|
418
|
-
? requestedTabId
|
|
419
|
-
: undefined,
|
|
420
363
|
enabled: pendingEnabled,
|
|
421
364
|
});
|
|
422
365
|
});
|
|
@@ -438,9 +381,7 @@ setupStatusMatrix.addEventListener('contextmenu', (event) => {
|
|
|
438
381
|
if (!(target instanceof HTMLElement)) {
|
|
439
382
|
return;
|
|
440
383
|
}
|
|
441
|
-
const actionTarget = target.closest(
|
|
442
|
-
'[data-context-kind][data-context-target]',
|
|
443
|
-
);
|
|
384
|
+
const actionTarget = target.closest('[data-context-kind][data-context-target]');
|
|
444
385
|
if (!(actionTarget instanceof HTMLElement)) {
|
|
445
386
|
hideSetupContextMenu();
|
|
446
387
|
return;
|
|
@@ -452,12 +393,7 @@ setupStatusMatrix.addEventListener('contextmenu', (event) => {
|
|
|
452
393
|
const copyText = actionTarget.dataset.contextCopyText;
|
|
453
394
|
const reinstallLabel = actionTarget.dataset.contextReinstallLabel;
|
|
454
395
|
const uninstallLabel = actionTarget.dataset.contextUninstallLabel;
|
|
455
|
-
if (
|
|
456
|
-
(kind !== 'mcp' && kind !== 'skill') ||
|
|
457
|
-
!targetKey ||
|
|
458
|
-
!copyLabel ||
|
|
459
|
-
!copyText
|
|
460
|
-
) {
|
|
396
|
+
if ((kind !== 'mcp' && kind !== 'skill') || !targetKey || !copyLabel || !copyText) {
|
|
461
397
|
hideSetupContextMenu();
|
|
462
398
|
return;
|
|
463
399
|
}
|
|
@@ -502,13 +438,13 @@ function renderState(state) {
|
|
|
502
438
|
state.setupStatusPending,
|
|
503
439
|
state.setupStatusError,
|
|
504
440
|
state.setupInstallPendingKey,
|
|
505
|
-
state.setupInstallError
|
|
441
|
+
state.setupInstallError
|
|
506
442
|
);
|
|
507
443
|
|
|
508
444
|
actionLog.replaceChildren(
|
|
509
445
|
...state.actionLog.map((entry, index, entries) =>
|
|
510
|
-
renderActionLogEntry(entry, state.setupStatus, entries, index)
|
|
511
|
-
)
|
|
446
|
+
renderActionLogEntry(entry, state.setupStatus, entries, index)
|
|
447
|
+
)
|
|
512
448
|
);
|
|
513
449
|
currentActionLog = state.actionLog;
|
|
514
450
|
updateActivityVisualizations();
|
|
@@ -565,15 +501,14 @@ function renderCurrentTab(currentTab) {
|
|
|
565
501
|
toggleButton.textContent = 'Disable Window Access';
|
|
566
502
|
toggleButton.disabled = false;
|
|
567
503
|
toggleButton.dataset.enabled = String(currentTab.enabled);
|
|
568
|
-
toggleErrorEl.textContent =
|
|
504
|
+
toggleErrorEl.textContent =
|
|
505
|
+
'This page cannot be interacted with. Switch to a normal web page to inspect and interact.';
|
|
569
506
|
toggleErrorEl.hidden = false;
|
|
570
507
|
controlSection.classList.remove('attention');
|
|
571
508
|
return;
|
|
572
509
|
}
|
|
573
510
|
|
|
574
|
-
toggleButton.textContent = currentTab.enabled
|
|
575
|
-
? 'Disable Window Access'
|
|
576
|
-
: 'Enable Window Access';
|
|
511
|
+
toggleButton.textContent = currentTab.enabled ? 'Disable Window Access' : 'Enable Window Access';
|
|
577
512
|
toggleButton.disabled = !currentTab.url;
|
|
578
513
|
toggleButton.dataset.enabled = String(currentTab.enabled);
|
|
579
514
|
controlSection.classList.toggle('attention', currentTab.accessRequested && !currentTab.enabled);
|
|
@@ -619,7 +554,8 @@ function renderAgentStatus(state) {
|
|
|
619
554
|
|
|
620
555
|
if (!currentTab) {
|
|
621
556
|
agentStatus.textContent = 'Window access unavailable';
|
|
622
|
-
agentStatusDetail.textContent =
|
|
557
|
+
agentStatusDetail.textContent =
|
|
558
|
+
'Open a normal web page in this Chrome window to enable Browser Bridge.';
|
|
623
559
|
agentDisclosure.hidden = false;
|
|
624
560
|
return;
|
|
625
561
|
}
|
|
@@ -628,25 +564,29 @@ function renderAgentStatus(state) {
|
|
|
628
564
|
|
|
629
565
|
if (currentTab.enabled && currentTab.restricted) {
|
|
630
566
|
agentStatus.textContent = 'Window access enabled';
|
|
631
|
-
agentStatusDetail.textContent =
|
|
567
|
+
agentStatusDetail.textContent =
|
|
568
|
+
'This page cannot be interacted with. Switch to a normal web page to use Browser Bridge.';
|
|
632
569
|
agentDisclosure.hidden = false;
|
|
633
570
|
return;
|
|
634
571
|
}
|
|
635
572
|
|
|
636
573
|
if (currentTab.enabled) {
|
|
637
574
|
agentStatus.textContent = 'Window access enabled';
|
|
638
|
-
agentStatusDetail.textContent =
|
|
575
|
+
agentStatusDetail.textContent =
|
|
576
|
+
'Browser Bridge is enabled for this Chrome window. Requests default to the active tab, or can target another tab in this window explicitly.';
|
|
639
577
|
return;
|
|
640
578
|
}
|
|
641
579
|
|
|
642
580
|
if (currentTab.accessRequested) {
|
|
643
581
|
agentStatus.textContent = 'Window access requested';
|
|
644
|
-
agentStatusDetail.textContent =
|
|
582
|
+
agentStatusDetail.textContent =
|
|
583
|
+
'An agent requested access for this Chrome window. Enable it to allow page inspection and interaction.';
|
|
645
584
|
return;
|
|
646
585
|
}
|
|
647
586
|
|
|
648
587
|
agentStatus.textContent = 'Window access';
|
|
649
|
-
agentStatusDetail.textContent =
|
|
588
|
+
agentStatusDetail.textContent =
|
|
589
|
+
'Enable Browser Bridge to let your connected agent inspect and interact with pages in this Chrome window.';
|
|
650
590
|
}
|
|
651
591
|
|
|
652
592
|
/**
|
|
@@ -655,9 +595,7 @@ function renderAgentStatus(state) {
|
|
|
655
595
|
* @returns {void}
|
|
656
596
|
*/
|
|
657
597
|
function renderNativeStatus(connected, error) {
|
|
658
|
-
const label = connected
|
|
659
|
-
? 'Native host connected'
|
|
660
|
-
: error || 'Native host disconnected';
|
|
598
|
+
const label = connected ? 'Native host connected' : error || 'Native host disconnected';
|
|
661
599
|
nativeIndicator.dataset.connected = String(connected);
|
|
662
600
|
nativeIndicator.title = label;
|
|
663
601
|
nativeIndicator.setAttribute('aria-label', label);
|
|
@@ -684,7 +622,9 @@ function renderNativeStatus(connected, error) {
|
|
|
684
622
|
} else if (!nativeDiagnosticTimer) {
|
|
685
623
|
nativeDiagnosticTimer = setTimeout(() => {
|
|
686
624
|
nativeDiagnosticTimer = null;
|
|
687
|
-
showSidepanelDiagnostic(
|
|
625
|
+
showSidepanelDiagnostic(
|
|
626
|
+
`Native host unreachable for 10s. Run: npm install -g @browserbridge/bbx && ${setupInstallCmd.textContent || 'bbx install'}`
|
|
627
|
+
);
|
|
688
628
|
}, NATIVE_DIAGNOSTIC_DELAY_MS);
|
|
689
629
|
}
|
|
690
630
|
}
|
|
@@ -698,7 +638,8 @@ function showSidepanelDiagnostic(message) {
|
|
|
698
638
|
if (!el) {
|
|
699
639
|
el = document.createElement('div');
|
|
700
640
|
el.id = 'native-diagnostic';
|
|
701
|
-
el.style.cssText =
|
|
641
|
+
el.style.cssText =
|
|
642
|
+
'padding:8px 12px;margin:8px 0;background:var(--status-badge-bg,#fef3cd);color:var(--text-primary,#856404);border-radius:6px;font-size:12px;line-height:1.4';
|
|
702
643
|
setupSection.after(el);
|
|
703
644
|
}
|
|
704
645
|
el.textContent = message;
|
|
@@ -788,7 +729,7 @@ function renderPromptExamples(setupStatus) {
|
|
|
788
729
|
|
|
789
730
|
examplesContent.replaceChildren(
|
|
790
731
|
createExamplesGroup('CLI skill', CLI_PROMPT_EXAMPLES),
|
|
791
|
-
createExamplesGroup('MCP', MCP_PROMPT_EXAMPLES)
|
|
732
|
+
createExamplesGroup('MCP', MCP_PROMPT_EXAMPLES)
|
|
792
733
|
);
|
|
793
734
|
}
|
|
794
735
|
|
|
@@ -868,40 +809,27 @@ function syncExclusiveDetailsSections(source, other) {
|
|
|
868
809
|
* @param {string | null} installError
|
|
869
810
|
* @returns {void}
|
|
870
811
|
*/
|
|
871
|
-
function renderSetupStatus(
|
|
872
|
-
setupStatus,
|
|
873
|
-
pending,
|
|
874
|
-
error,
|
|
875
|
-
installPendingKey,
|
|
876
|
-
installError,
|
|
877
|
-
) {
|
|
812
|
+
function renderSetupStatus(setupStatus, pending, error, installPendingKey, installError) {
|
|
878
813
|
if (!setupStatus && pending) {
|
|
879
814
|
setupStatusSummaryNote.hidden = true;
|
|
880
815
|
setupStatusSummaryNote.textContent = '';
|
|
881
816
|
setupStatusNote.textContent = 'Checking global host setup…';
|
|
882
817
|
setupStatusNote.hidden = false;
|
|
883
|
-
setupStatusMatrix.replaceChildren(
|
|
884
|
-
createStatusPlaceholder('Checking detected clients…'),
|
|
885
|
-
);
|
|
818
|
+
setupStatusMatrix.replaceChildren(createStatusPlaceholder('Checking detected clients…'));
|
|
886
819
|
return;
|
|
887
820
|
}
|
|
888
821
|
|
|
889
822
|
if (!setupStatus) {
|
|
890
823
|
setupStatusSummaryNote.hidden = true;
|
|
891
824
|
setupStatusSummaryNote.textContent = '';
|
|
892
|
-
setupStatusNote.textContent =
|
|
893
|
-
installError || error || 'Global host setup is unavailable.';
|
|
825
|
+
setupStatusNote.textContent = installError || error || 'Global host setup is unavailable.';
|
|
894
826
|
setupStatusNote.hidden = false;
|
|
895
|
-
setupStatusMatrix.replaceChildren(
|
|
896
|
-
createStatusPlaceholder('No host setup status yet.'),
|
|
897
|
-
);
|
|
827
|
+
setupStatusMatrix.replaceChildren(createStatusPlaceholder('No host setup status yet.'));
|
|
898
828
|
return;
|
|
899
829
|
}
|
|
900
830
|
|
|
901
831
|
const scopeNote =
|
|
902
|
-
setupStatus.scope === 'global'
|
|
903
|
-
? 'Global installs only'
|
|
904
|
-
: 'Project installs only';
|
|
832
|
+
setupStatus.scope === 'global' ? 'Global installs only' : 'Project installs only';
|
|
905
833
|
setupStatusSummaryNote.textContent = `* ${scopeNote}`;
|
|
906
834
|
setupStatusSummaryNote.hidden = false;
|
|
907
835
|
|
|
@@ -926,7 +854,7 @@ function renderSetupStatus(
|
|
|
926
854
|
const rows = buildSetupMatrixRows(setupStatus);
|
|
927
855
|
if (!rows.length) {
|
|
928
856
|
setupStatusMatrix.replaceChildren(
|
|
929
|
-
createStatusPlaceholder('No supported clients or agents were detected.')
|
|
857
|
+
createStatusPlaceholder('No supported clients or agents were detected.')
|
|
930
858
|
);
|
|
931
859
|
return;
|
|
932
860
|
}
|
|
@@ -1018,11 +946,7 @@ function shouldRenderMcpClientRow(entry) {
|
|
|
1018
946
|
* @returns {boolean}
|
|
1019
947
|
*/
|
|
1020
948
|
function shouldRenderSkillTargetRow(entry) {
|
|
1021
|
-
return (
|
|
1022
|
-
entry.detected ||
|
|
1023
|
-
entry.installed ||
|
|
1024
|
-
entry.skills.some((skill) => skill.exists)
|
|
1025
|
-
);
|
|
949
|
+
return entry.detected || entry.installed || entry.skills.some((skill) => skill.exists);
|
|
1026
950
|
}
|
|
1027
951
|
|
|
1028
952
|
/**
|
|
@@ -1098,7 +1022,7 @@ function renderMcpMatrixCell(row, installPendingKey) {
|
|
|
1098
1022
|
installPendingKey === getInstallKey('mcp', row.key),
|
|
1099
1023
|
'install',
|
|
1100
1024
|
'Install',
|
|
1101
|
-
'...'
|
|
1025
|
+
'...'
|
|
1102
1026
|
);
|
|
1103
1027
|
button.title = 'Install Browser Bridge MCP for generic agents';
|
|
1104
1028
|
return button;
|
|
@@ -1121,7 +1045,7 @@ function renderMcpMatrixCell(row, installPendingKey) {
|
|
|
1121
1045
|
installPendingKey === getInstallKey('mcp', row.key),
|
|
1122
1046
|
'install',
|
|
1123
1047
|
'Install',
|
|
1124
|
-
'...'
|
|
1048
|
+
'...'
|
|
1125
1049
|
);
|
|
1126
1050
|
button.title = entry.configPath;
|
|
1127
1051
|
return button;
|
|
@@ -1140,9 +1064,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
|
|
|
1140
1064
|
const reinstallLabel = getSkillReinstallLabel(entry);
|
|
1141
1065
|
const uninstallLabel = getSkillUninstallLabel(entry);
|
|
1142
1066
|
|
|
1143
|
-
const installable = entry.skills.every(
|
|
1144
|
-
(skill) => !skill.exists || skill.managed,
|
|
1145
|
-
);
|
|
1067
|
+
const installable = entry.skills.every((skill) => !skill.exists || skill.managed);
|
|
1146
1068
|
if (!installable) {
|
|
1147
1069
|
return createMatrixBadge('Custom', false, entry.basePath, {
|
|
1148
1070
|
kind: 'skill',
|
|
@@ -1168,7 +1090,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
|
|
|
1168
1090
|
installPendingKey === getInstallKey('skill', row.key),
|
|
1169
1091
|
'update',
|
|
1170
1092
|
'Update',
|
|
1171
|
-
'Updating…'
|
|
1093
|
+
'Updating…'
|
|
1172
1094
|
);
|
|
1173
1095
|
button.title = createSkillCellTitle(entry);
|
|
1174
1096
|
assignSetupContext(button, {
|
|
@@ -1188,7 +1110,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
|
|
|
1188
1110
|
installPendingKey === getInstallKey('skill', row.key),
|
|
1189
1111
|
'install',
|
|
1190
1112
|
'Install',
|
|
1191
|
-
'...'
|
|
1113
|
+
'...'
|
|
1192
1114
|
);
|
|
1193
1115
|
button.title = entry.basePath;
|
|
1194
1116
|
return button;
|
|
@@ -1203,14 +1125,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
|
|
|
1203
1125
|
* @param {string} pendingLabel
|
|
1204
1126
|
* @returns {HTMLButtonElement}
|
|
1205
1127
|
*/
|
|
1206
|
-
function createSetupActionButton(
|
|
1207
|
-
kind,
|
|
1208
|
-
target,
|
|
1209
|
-
pending,
|
|
1210
|
-
variant,
|
|
1211
|
-
label,
|
|
1212
|
-
pendingLabel,
|
|
1213
|
-
) {
|
|
1128
|
+
function createSetupActionButton(kind, target, pending, variant, label, pendingLabel) {
|
|
1214
1129
|
const button = document.createElement('button');
|
|
1215
1130
|
button.type = 'button';
|
|
1216
1131
|
button.className = 'setup-install-button';
|
|
@@ -1349,7 +1264,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
|
|
|
1349
1264
|
});
|
|
1350
1265
|
hideSetupContextMenu();
|
|
1351
1266
|
},
|
|
1352
|
-
{ once: true }
|
|
1267
|
+
{ once: true }
|
|
1353
1268
|
);
|
|
1354
1269
|
setupContextMenu.append(copyButton);
|
|
1355
1270
|
|
|
@@ -1369,7 +1284,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
|
|
|
1369
1284
|
});
|
|
1370
1285
|
hideSetupContextMenu();
|
|
1371
1286
|
},
|
|
1372
|
-
{ once: true }
|
|
1287
|
+
{ once: true }
|
|
1373
1288
|
);
|
|
1374
1289
|
setupContextMenu.append(reinstallButton);
|
|
1375
1290
|
}
|
|
@@ -1390,7 +1305,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
|
|
|
1390
1305
|
});
|
|
1391
1306
|
hideSetupContextMenu();
|
|
1392
1307
|
},
|
|
1393
|
-
{ once: true }
|
|
1308
|
+
{ once: true }
|
|
1394
1309
|
);
|
|
1395
1310
|
setupContextMenu.append(uninstallButton);
|
|
1396
1311
|
}
|
|
@@ -1477,8 +1392,7 @@ function renderActionLogEntry(entry, setupStatus, entries, index) {
|
|
|
1477
1392
|
const badges = document.createElement('span');
|
|
1478
1393
|
badges.className = 'activity-badges';
|
|
1479
1394
|
|
|
1480
|
-
const showScope =
|
|
1481
|
-
!(requestedTabId != null && requestedTabId > 0) && entry.url;
|
|
1395
|
+
const showScope = !(requestedTabId != null && requestedTabId > 0) && entry.url;
|
|
1482
1396
|
if (showScope) {
|
|
1483
1397
|
const scopeLink = document.createElement('a');
|
|
1484
1398
|
scopeLink.className = 'activity-scope-link';
|
|
@@ -1632,7 +1546,7 @@ function alignHistogramEndAt(value) {
|
|
|
1632
1546
|
function buildActivityHistogram(entries) {
|
|
1633
1547
|
const latestAt = entries.reduce(
|
|
1634
1548
|
(maxAt, entry) => Math.max(maxAt, Number.isFinite(entry.at) ? entry.at : 0),
|
|
1635
|
-
0
|
|
1549
|
+
0
|
|
1636
1550
|
);
|
|
1637
1551
|
const endAt = alignHistogramEndAt(Math.max(Date.now(), latestAt));
|
|
1638
1552
|
const startAt = endAt - ACTIVITY_HISTOGRAM_WINDOW_MS;
|
|
@@ -1643,9 +1557,7 @@ function buildActivityHistogram(entries) {
|
|
|
1643
1557
|
}));
|
|
1644
1558
|
|
|
1645
1559
|
/** @type {Map<number, Map<string, number>>} */
|
|
1646
|
-
const bucketFamilies = new Map(
|
|
1647
|
-
buckets.map((_, index) => [index, new Map()]),
|
|
1648
|
-
);
|
|
1560
|
+
const bucketFamilies = new Map(buckets.map((_, index) => [index, new Map()]));
|
|
1649
1561
|
let totalTokens = 0;
|
|
1650
1562
|
|
|
1651
1563
|
for (const entry of entries) {
|
|
@@ -1659,7 +1571,7 @@ function buildActivityHistogram(entries) {
|
|
|
1659
1571
|
|
|
1660
1572
|
const offset = Math.min(
|
|
1661
1573
|
ACTIVITY_HISTOGRAM_BARS - 1,
|
|
1662
|
-
Math.max(0, Math.floor((entry.at - startAt) / ACTIVITY_HISTOGRAM_BUCKET_MS))
|
|
1574
|
+
Math.max(0, Math.floor((entry.at - startAt) / ACTIVITY_HISTOGRAM_BUCKET_MS))
|
|
1663
1575
|
);
|
|
1664
1576
|
const family = getHistogramMethodFamily(entry.method);
|
|
1665
1577
|
const familyTotals = /** @type {Map<string, number>} */ (bucketFamilies.get(offset));
|
|
@@ -1670,12 +1582,13 @@ function buildActivityHistogram(entries) {
|
|
|
1670
1582
|
|
|
1671
1583
|
for (let index = 0; index < buckets.length; index += 1) {
|
|
1672
1584
|
const familyTotals = /** @type {Map<string, number>} */ (bucketFamilies.get(index));
|
|
1673
|
-
buckets[index].segments =
|
|
1674
|
-
|
|
1675
|
-
family
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1585
|
+
buckets[index].segments =
|
|
1586
|
+
/** @type {typeof buckets[number]['segments']} */ (
|
|
1587
|
+
HISTOGRAM_METHOD_FAMILIES.map((family) => ({
|
|
1588
|
+
family,
|
|
1589
|
+
tokens: familyTotals.get(family) ?? 0,
|
|
1590
|
+
})).filter((segment) => segment.tokens > 0)
|
|
1591
|
+
);
|
|
1679
1592
|
}
|
|
1680
1593
|
|
|
1681
1594
|
return {
|
|
@@ -1703,12 +1616,7 @@ function renderActivityHistogram(histogram) {
|
|
|
1703
1616
|
activityHistogram.hidden = false;
|
|
1704
1617
|
activityHistogramRange.textContent = '10m window · 30s bins';
|
|
1705
1618
|
activityHistogramBars.replaceChildren(
|
|
1706
|
-
...histogram.buckets.map((bucket) =>
|
|
1707
|
-
createHistogramBar(
|
|
1708
|
-
bucket,
|
|
1709
|
-
maxTokens,
|
|
1710
|
-
)
|
|
1711
|
-
),
|
|
1619
|
+
...histogram.buckets.map((bucket) => createHistogramBar(bucket, maxTokens))
|
|
1712
1620
|
);
|
|
1713
1621
|
}
|
|
1714
1622
|
|
|
@@ -1718,9 +1626,7 @@ function renderActivityHistogram(histogram) {
|
|
|
1718
1626
|
*/
|
|
1719
1627
|
function formatCompactCount(value) {
|
|
1720
1628
|
if (value >= 1000) {
|
|
1721
|
-
const compact = value >= 10000
|
|
1722
|
-
? Math.round(value / 1000)
|
|
1723
|
-
: Math.round(value / 100) / 10;
|
|
1629
|
+
const compact = value >= 10000 ? Math.round(value / 1000) : Math.round(value / 100) / 10;
|
|
1724
1630
|
return `${compact}k`;
|
|
1725
1631
|
}
|
|
1726
1632
|
return String(value);
|
|
@@ -1751,9 +1657,7 @@ function getAggregateCostClass(approxTokens) {
|
|
|
1751
1657
|
function createHistogramBar(bucket, maxTokens) {
|
|
1752
1658
|
const bar = document.createElement('span');
|
|
1753
1659
|
const ratio = Math.log1p(bucket.totalTokens) / Math.log1p(maxTokens);
|
|
1754
|
-
const height = bucket.totalTokens > 0
|
|
1755
|
-
? Math.max(14, Math.round(ratio * 100))
|
|
1756
|
-
: 6;
|
|
1660
|
+
const height = bucket.totalTokens > 0 ? Math.max(14, Math.round(ratio * 100)) : 6;
|
|
1757
1661
|
bar.className = 'activity-histogram-bar';
|
|
1758
1662
|
bar.style.height = `${height}%`;
|
|
1759
1663
|
|
|
@@ -1762,21 +1666,21 @@ function createHistogramBar(bucket, maxTokens) {
|
|
|
1762
1666
|
bar.title = `${new Date(bucket.bucketStart).toLocaleTimeString()} · no activity`;
|
|
1763
1667
|
bar.setAttribute(
|
|
1764
1668
|
'aria-label',
|
|
1765
|
-
`${new Date(bucket.bucketStart).toLocaleTimeString()}, no token activity
|
|
1669
|
+
`${new Date(bucket.bucketStart).toLocaleTimeString()}, no token activity`
|
|
1766
1670
|
);
|
|
1767
1671
|
return bar;
|
|
1768
1672
|
}
|
|
1769
1673
|
|
|
1770
1674
|
const tooltipParts = bucket.segments.map(
|
|
1771
|
-
(segment) => `${segment.family}: ${segment.tokens.toLocaleString()} tok
|
|
1675
|
+
(segment) => `${segment.family}: ${segment.tokens.toLocaleString()} tok`
|
|
1772
1676
|
);
|
|
1773
1677
|
bar.title = `${new Date(bucket.bucketStart).toLocaleTimeString()} · ${bucket.totalTokens.toLocaleString()} tok\n${tooltipParts.join('\n')}`;
|
|
1774
1678
|
bar.setAttribute(
|
|
1775
1679
|
'aria-label',
|
|
1776
|
-
`${new Date(bucket.bucketStart).toLocaleTimeString()}, approximately ${bucket.totalTokens.toLocaleString()} tokens
|
|
1680
|
+
`${new Date(bucket.bucketStart).toLocaleTimeString()}, approximately ${bucket.totalTokens.toLocaleString()} tokens`
|
|
1777
1681
|
);
|
|
1778
1682
|
bar.append(
|
|
1779
|
-
...bucket.segments.map((segment) => createHistogramSegment(segment, bucket.totalTokens))
|
|
1683
|
+
...bucket.segments.map((segment) => createHistogramSegment(segment, bucket.totalTokens))
|
|
1780
1684
|
);
|
|
1781
1685
|
return bar;
|
|
1782
1686
|
}
|
|
@@ -1811,13 +1715,21 @@ function getHistogramMethodFamily(method) {
|
|
|
1811
1715
|
if (method.startsWith('styles.')) {
|
|
1812
1716
|
return 'style';
|
|
1813
1717
|
}
|
|
1814
|
-
if (
|
|
1718
|
+
if (
|
|
1719
|
+
method.startsWith('input.') ||
|
|
1720
|
+
method.startsWith('navigation.') ||
|
|
1721
|
+
method.startsWith('viewport.')
|
|
1722
|
+
) {
|
|
1815
1723
|
return 'input';
|
|
1816
1724
|
}
|
|
1817
1725
|
if (method.startsWith('patch.')) {
|
|
1818
1726
|
return 'patch';
|
|
1819
1727
|
}
|
|
1820
|
-
if (
|
|
1728
|
+
if (
|
|
1729
|
+
method.startsWith('screenshot.') ||
|
|
1730
|
+
method.startsWith('cdp.') ||
|
|
1731
|
+
method.startsWith('performance.')
|
|
1732
|
+
) {
|
|
1821
1733
|
return 'capture';
|
|
1822
1734
|
}
|
|
1823
1735
|
return 'other';
|
|
@@ -1830,10 +1742,12 @@ function getHistogramMethodFamily(method) {
|
|
|
1830
1742
|
*/
|
|
1831
1743
|
function countRecentExpensiveRepeats(entries, index) {
|
|
1832
1744
|
const current = entries[index];
|
|
1833
|
-
const currentCostClass =
|
|
1834
|
-
? current.summaryCostClass
|
|
1835
|
-
|
|
1836
|
-
|
|
1745
|
+
const currentCostClass =
|
|
1746
|
+
current?.summaryTokens > 0 ? current.summaryCostClass : current?.costClass;
|
|
1747
|
+
if (
|
|
1748
|
+
!current ||
|
|
1749
|
+
(!current.debuggerBacked && currentCostClass !== 'heavy' && currentCostClass !== 'extreme')
|
|
1750
|
+
) {
|
|
1837
1751
|
return 0;
|
|
1838
1752
|
}
|
|
1839
1753
|
|
|
@@ -1843,10 +1757,13 @@ function countRecentExpensiveRepeats(entries, index) {
|
|
|
1843
1757
|
if (!candidate || candidate.method !== current.method) {
|
|
1844
1758
|
break;
|
|
1845
1759
|
}
|
|
1846
|
-
const candidateCostClass =
|
|
1847
|
-
? candidate.summaryCostClass
|
|
1848
|
-
|
|
1849
|
-
|
|
1760
|
+
const candidateCostClass =
|
|
1761
|
+
candidate.summaryTokens > 0 ? candidate.summaryCostClass : candidate.costClass;
|
|
1762
|
+
if (
|
|
1763
|
+
candidate.debuggerBacked ||
|
|
1764
|
+
candidateCostClass === 'heavy' ||
|
|
1765
|
+
candidateCostClass === 'extreme'
|
|
1766
|
+
) {
|
|
1850
1767
|
count += 1;
|
|
1851
1768
|
}
|
|
1852
1769
|
}
|