@flrande/bak-extension 0.6.4 → 0.6.6

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.
@@ -1 +1 @@
1
- 2026-03-12T15:53:45.600Z
1
+ 2026-03-13T07:15:48.660Z
@@ -62,7 +62,7 @@
62
62
  // package.json
63
63
  var package_default = {
64
64
  name: "@flrande/bak-extension",
65
- version: "0.6.4",
65
+ version: "0.6.6",
66
66
  type: "module",
67
67
  scripts: {
68
68
  build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
@@ -573,13 +573,14 @@
573
573
  state.activeTabId = null;
574
574
  state.primaryTabId = null;
575
575
  window2 = createdWindow;
576
- tabs = await this.waitForWindowTabs(createdWindow.id);
576
+ const initialTab = typeof createdWindow.initialTabId === "number" ? await this.waitForTrackedTab(createdWindow.initialTabId, createdWindow.id) : null;
577
+ tabs = initialTab ? [initialTab] : await this.waitForWindowTabs(createdWindow.id);
577
578
  state.tabIds = tabs.map((tab) => tab.id);
578
579
  if (state.primaryTabId === null) {
579
- state.primaryTabId = tabs[0]?.id ?? null;
580
+ state.primaryTabId = initialTab?.id ?? tabs[0]?.id ?? null;
580
581
  }
581
582
  if (state.activeTabId === null) {
582
- state.activeTabId = tabs.find((tab) => tab.active)?.id ?? tabs[0]?.id ?? null;
583
+ state.activeTabId = tabs.find((tab) => tab.active)?.id ?? initialTab?.id ?? tabs[0]?.id ?? null;
583
584
  }
584
585
  repairActions.push(created ? "created-window" : "recreated-window");
585
586
  }
@@ -838,22 +839,9 @@
838
839
  await this.browser.closeTab(resolvedTabId);
839
840
  const remainingTabIds = ensured.binding.tabIds.filter((candidate) => candidate !== resolvedTabId);
840
841
  if (remainingTabIds.length === 0) {
841
- const emptied = {
842
- id: ensured.binding.id,
843
- label: ensured.binding.label,
844
- color: ensured.binding.color,
845
- windowId: null,
846
- groupId: null,
847
- tabIds: [],
848
- activeTabId: null,
849
- primaryTabId: null
850
- };
851
- await this.storage.save(emptied);
842
+ await this.storage.delete(ensured.binding.id);
852
843
  return {
853
- binding: {
854
- ...emptied,
855
- tabs: []
856
- },
844
+ binding: null,
857
845
  closedTabId: resolvedTabId
858
846
  };
859
847
  }
@@ -1094,7 +1082,8 @@
1094
1082
  url: seedUrl || DEFAULT_SESSION_BINDING_URL,
1095
1083
  focused: false
1096
1084
  });
1097
- const recreatedTabs = await this.waitForWindowTabs(window2.id);
1085
+ const initialTab = typeof window2.initialTabId === "number" ? await this.waitForTrackedTab(window2.initialTabId, window2.id) : null;
1086
+ const recreatedTabs = initialTab ? [initialTab] : await this.waitForWindowTabs(window2.id);
1098
1087
  const firstTab = recreatedTabs[0] ?? null;
1099
1088
  const tabIdMap = /* @__PURE__ */ new Map();
1100
1089
  if (sourceTabs[0] && firstTab) {
@@ -1333,11 +1322,14 @@
1333
1322
  var ws = null;
1334
1323
  var reconnectTimer = null;
1335
1324
  var nextReconnectInMs = null;
1325
+ var nextReconnectAt = null;
1336
1326
  var reconnectAttempt = 0;
1337
1327
  var lastError = null;
1338
1328
  var manualDisconnect = false;
1339
1329
  var sessionBindingStateMutationQueue = Promise.resolve();
1340
1330
  var preserveHumanFocusDepth = 0;
1331
+ var lastBindingUpdateAt = null;
1332
+ var lastBindingUpdateReason = null;
1341
1333
  async function getConfig() {
1342
1334
  const stored = await chrome.storage.local.get([STORAGE_KEY_TOKEN, STORAGE_KEY_PORT, STORAGE_KEY_DEBUG_RICH_TEXT]);
1343
1335
  return {
@@ -1374,6 +1366,7 @@
1374
1366
  reconnectTimer = null;
1375
1367
  }
1376
1368
  nextReconnectInMs = null;
1369
+ nextReconnectAt = null;
1377
1370
  }
1378
1371
  function sendResponse(payload) {
1379
1372
  if (ws && ws.readyState === WebSocket.OPEN) {
@@ -1469,6 +1462,65 @@
1469
1462
  async function listSessionBindingStates() {
1470
1463
  return Object.values(await loadSessionBindingStateMap());
1471
1464
  }
1465
+ function summarizeSessionBindings(states) {
1466
+ const items = states.map((state) => {
1467
+ const detached = state.windowId === null || state.tabIds.length === 0;
1468
+ return {
1469
+ id: state.id,
1470
+ label: state.label,
1471
+ tabCount: state.tabIds.length,
1472
+ activeTabId: state.activeTabId,
1473
+ windowId: state.windowId,
1474
+ groupId: state.groupId,
1475
+ detached
1476
+ };
1477
+ });
1478
+ return {
1479
+ count: items.length,
1480
+ attachedCount: items.filter((item) => !item.detached).length,
1481
+ detachedCount: items.filter((item) => item.detached).length,
1482
+ tabCount: items.reduce((sum, item) => sum + item.tabCount, 0),
1483
+ items
1484
+ };
1485
+ }
1486
+ async function buildPopupState() {
1487
+ const config = await getConfig();
1488
+ const sessionBindings = summarizeSessionBindings(await listSessionBindingStates());
1489
+ const reconnectRemainingMs = nextReconnectAt === null ? null : Math.max(0, nextReconnectAt - Date.now());
1490
+ let connectionState;
1491
+ if (!config.token) {
1492
+ connectionState = "missing-token";
1493
+ } else if (ws?.readyState === WebSocket.OPEN) {
1494
+ connectionState = "connected";
1495
+ } else if (ws?.readyState === WebSocket.CONNECTING) {
1496
+ connectionState = "connecting";
1497
+ } else if (manualDisconnect) {
1498
+ connectionState = "manual";
1499
+ } else if (nextReconnectInMs !== null) {
1500
+ connectionState = "reconnecting";
1501
+ } else {
1502
+ connectionState = "disconnected";
1503
+ }
1504
+ return {
1505
+ ok: true,
1506
+ connected: ws?.readyState === WebSocket.OPEN,
1507
+ connectionState,
1508
+ hasToken: Boolean(config.token),
1509
+ port: config.port,
1510
+ wsUrl: `ws://127.0.0.1:${config.port}/extension`,
1511
+ debugRichText: config.debugRichText,
1512
+ lastError: lastError?.message ?? null,
1513
+ lastErrorAt: lastError?.at ?? null,
1514
+ lastErrorContext: lastError?.context ?? null,
1515
+ reconnectAttempt,
1516
+ nextReconnectInMs: reconnectRemainingMs,
1517
+ manualDisconnect,
1518
+ extensionVersion: EXTENSION_VERSION,
1519
+ lastBindingUpdateAt,
1520
+ lastBindingUpdateReason,
1521
+ sessionBindings
1522
+ };
1523
+ }
1472
1524
  async function saveSessionBindingState(state) {
1473
1525
  await mutateSessionBindingStateMap((stateMap) => {
1474
1526
  stateMap[state.id] = state;
@@ -1492,6 +1544,8 @@
1492
1544
  };
1493
1545
  }
1494
1546
  function emitSessionBindingUpdated(bindingId, reason, state, extras = {}) {
1547
+ lastBindingUpdateAt = Date.now();
1548
+ lastBindingUpdateReason = reason;
1495
1549
  sendEvent("sessionBinding.updated", {
1496
1550
  bindingId,
1497
1551
  reason,
@@ -1556,14 +1610,42 @@
1556
1610
  },
1557
1611
  async createWindow(options) {
1558
1612
  const previouslyFocusedWindow = options.focused === true ? null : (await chrome.windows.getAll()).find((window2) => window2.focused === true && typeof window2.id === "number") ?? null;
1559
- const previouslyFocusedTab = previouslyFocusedWindow?.id !== void 0 ? (await chrome.tabs.query({ windowId: previouslyFocusedWindow.id, active: true })).find((tab) => typeof tab.id === "number") ?? null : null;
1560
- const created = await chrome.windows.create({
1561
- url: options.url ?? "about:blank",
1613
+ const previouslyFocusedTabs = previouslyFocusedWindow?.id !== void 0 ? await chrome.tabs.query({ windowId: previouslyFocusedWindow.id }) : [];
1614
+ const previouslyFocusedTabIds = new Set(
1615
+ previouslyFocusedTabs.flatMap((tab) => typeof tab.id === "number" ? [tab.id] : [])
1616
+ );
1617
+ const previouslyFocusedTab = previouslyFocusedTabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? null;
1618
+ const desiredUrl = options.url ?? "about:blank";
1619
+ let created = await chrome.windows.create({
1620
+ url: desiredUrl,
1562
1621
  focused: true
1563
1622
  });
1564
1623
  if (!created || typeof created.id !== "number") {
1565
1624
  throw new Error("Window missing id");
1566
1625
  }
1626
+ const pickSeedTab = async (windowId) => {
1627
+ const tabs = await chrome.tabs.query({ windowId });
1628
+ const newlyCreatedTab = windowId === previouslyFocusedWindow?.id ? tabs.find((tab) => typeof tab.id === "number" && !previouslyFocusedTabIds.has(tab.id)) : null;
1629
+ const normalizedDesiredUrl = normalizeComparableTabUrl(desiredUrl);
1630
+ return newlyCreatedTab ?? tabs.find((tab) => {
1631
+ const pendingUrl = "pendingUrl" in tab && typeof tab.pendingUrl === "string" ? tab.pendingUrl : "";
1632
+ return normalizeComparableTabUrl(tab.url ?? pendingUrl) === normalizedDesiredUrl;
1633
+ }) ?? tabs.find((tab) => tab.active === true && typeof tab.id === "number") ?? tabs.find((tab) => typeof tab.id === "number") ?? null;
1634
+ };
1635
+ let seedTab = await pickSeedTab(created.id);
1636
+ const createdWindowTabs = await chrome.tabs.query({ windowId: created.id });
1637
+ const createdWindowReusedFocusedWindow = previouslyFocusedWindow?.id === created.id;
1638
+ const createdWindowLooksDirty = createdWindowTabs.length > 1;
1639
+ if ((createdWindowReusedFocusedWindow || createdWindowLooksDirty) && typeof seedTab?.id === "number") {
1640
+ created = await chrome.windows.create({
1641
+ tabId: seedTab.id,
1642
+ focused: true
1643
+ });
1644
+ if (!created || typeof created.id !== "number") {
1645
+ throw new Error("Lifted window missing id");
1646
+ }
1647
+ seedTab = await pickSeedTab(created.id);
1648
+ }
1567
1649
  if (options.focused !== true && previouslyFocusedWindow?.id && previouslyFocusedWindow.id !== created.id) {
1568
1650
  await chrome.windows.update(previouslyFocusedWindow.id, { focused: true });
1569
1651
  if (typeof previouslyFocusedTab?.id === "number") {
@@ -1573,7 +1655,8 @@
1573
1655
  const finalWindow = await chrome.windows.get(created.id);
1574
1656
  return {
1575
1657
  id: finalWindow.id,
1576
- focused: Boolean(finalWindow.focused)
1658
+ focused: Boolean(finalWindow.focused),
1659
+ initialTabId: seedTab?.id ?? null
1577
1660
  };
1578
1661
  },
1579
1662
  async updateWindow(windowId, options) {
@@ -3311,9 +3394,11 @@
3311
3394
  const delayMs = computeReconnectDelayMs(reconnectAttempt);
3312
3395
  reconnectAttempt += 1;
3313
3396
  nextReconnectInMs = delayMs;
3397
+ nextReconnectAt = Date.now() + delayMs;
3314
3398
  reconnectTimer = setTimeout(() => {
3315
3399
  reconnectTimer = null;
3316
3400
  nextReconnectInMs = null;
3401
+ nextReconnectAt = null;
3317
3402
  void connectWebSocket();
3318
3403
  }, delayMs);
3319
3404
  if (!lastError) {
@@ -3334,19 +3419,23 @@
3334
3419
  return;
3335
3420
  }
3336
3421
  const url = `ws://127.0.0.1:${config.port}/extension?token=${encodeURIComponent(config.token)}`;
3337
- ws = new WebSocket(url);
3338
- ws.addEventListener("open", () => {
3422
+ const socket = new WebSocket(url);
3423
+ ws = socket;
3424
+ socket.addEventListener("open", () => {
3425
+ if (ws !== socket) {
3426
+ return;
3427
+ }
3339
3428
  manualDisconnect = false;
3340
3429
  reconnectAttempt = 0;
3341
3430
  lastError = null;
3342
- ws?.send(JSON.stringify({
3431
+ socket.send(JSON.stringify({
3343
3432
  type: "hello",
3344
3433
  role: "extension",
3345
3434
  version: EXTENSION_VERSION,
3346
3435
  ts: Date.now()
3347
3436
  }));
3348
3437
  });
3349
- ws.addEventListener("message", (event) => {
3438
+ socket.addEventListener("message", (event) => {
3350
3439
  try {
3351
3440
  const request = JSON.parse(String(event.data));
3352
3441
  if (!request.id || !request.method) {
@@ -3367,13 +3456,19 @@
3367
3456
  });
3368
3457
  }
3369
3458
  });
3370
- ws.addEventListener("close", () => {
3459
+ socket.addEventListener("close", () => {
3460
+ if (ws !== socket) {
3461
+ return;
3462
+ }
3371
3463
  ws = null;
3372
3464
  scheduleReconnect("socket-closed");
3373
3465
  });
3374
- ws.addEventListener("error", () => {
3466
+ socket.addEventListener("error", () => {
3467
+ if (ws !== socket) {
3468
+ return;
3469
+ }
3375
3470
  setRuntimeError("Cannot connect to bak cli", "socket");
3376
- ws?.close();
3471
+ socket.close();
3377
3472
  });
3378
3473
  }
3379
3474
  chrome.tabs.onRemoved.addListener((tabId) => {
@@ -3385,6 +3480,11 @@
3385
3480
  continue;
3386
3481
  }
3387
3482
  const nextTabIds = state.tabIds.filter((id) => id !== tabId);
3483
+ if (nextTabIds.length === 0) {
3484
+ delete stateMap[bindingId];
3485
+ updates.push({ bindingId, state: null });
3486
+ continue;
3487
+ }
3388
3488
  const fallbackTabId = nextTabIds[0] ?? null;
3389
3489
  const nextState = {
3390
3490
  ...state,
@@ -3440,16 +3540,8 @@
3440
3540
  if (state.windowId !== windowId) {
3441
3541
  continue;
3442
3542
  }
3443
- const nextState = {
3444
- ...state,
3445
- windowId: null,
3446
- groupId: null,
3447
- tabIds: [],
3448
- activeTabId: null,
3449
- primaryTabId: null
3450
- };
3451
- stateMap[bindingId] = nextState;
3452
- updates.push({ bindingId, state: nextState });
3543
+ delete stateMap[bindingId];
3544
+ updates.push({ bindingId, state: null });
3453
3545
  }
3454
3546
  return updates;
3455
3547
  }).then((updates) => {
@@ -3468,8 +3560,9 @@
3468
3560
  chrome.runtime.onMessage.addListener((message, _sender, sendResponse2) => {
3469
3561
  if (message?.type === "bak.updateConfig") {
3470
3562
  manualDisconnect = false;
3563
+ const token = typeof message.token === "string" ? message.token.trim() : "";
3471
3564
  void setConfig({
3472
- token: message.token,
3565
+ ...token ? { token } : {},
3473
3566
  port: Number(message.port ?? DEFAULT_PORT),
3474
3567
  debugRichText: message.debugRichText === true
3475
3568
  }).then(() => {
@@ -3479,19 +3572,8 @@
3479
3572
  return true;
3480
3573
  }
3481
3574
  if (message?.type === "bak.getState") {
3482
- void getConfig().then((config) => {
3483
- sendResponse2({
3484
- ok: true,
3485
- connected: ws?.readyState === WebSocket.OPEN,
3486
- hasToken: Boolean(config.token),
3487
- port: config.port,
3488
- debugRichText: config.debugRichText,
3489
- lastError: lastError?.message ?? null,
3490
- lastErrorAt: lastError?.at ?? null,
3491
- lastErrorContext: lastError?.context ?? null,
3492
- reconnectAttempt,
3493
- nextReconnectInMs
3494
- });
3575
+ void buildPopupState().then((state) => {
3576
+ sendResponse2(state);
3495
3577
  });
3496
3578
  return true;
3497
3579
  }
@@ -3499,11 +3581,21 @@
3499
3581
  manualDisconnect = true;
3500
3582
  clearReconnectTimer();
3501
3583
  reconnectAttempt = 0;
3584
+ lastError = null;
3502
3585
  ws?.close();
3503
3586
  ws = null;
3504
3587
  sendResponse2({ ok: true });
3505
3588
  return false;
3506
3589
  }
3590
+ if (message?.type === "bak.reconnectNow") {
3591
+ manualDisconnect = false;
3592
+ clearReconnectTimer();
3593
+ reconnectAttempt = 0;
3594
+ ws?.close();
3595
+ ws = null;
3596
+ void connectWebSocket().then(() => sendResponse2({ ok: true }));
3597
+ return true;
3598
+ }
3507
3599
  return false;
3508
3600
  });
3509
3601
  })();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Browser Agent Kit",
4
- "version": "0.6.4",
4
+ "version": "0.6.6",
5
5
  "action": {
6
6
  "default_popup": "popup.html"
7
7
  },
@@ -6,18 +6,97 @@
6
6
  var portInput = document.getElementById("port");
7
7
  var debugRichTextInput = document.getElementById("debugRichText");
8
8
  var saveBtn = document.getElementById("save");
9
+ var reconnectBtn = document.getElementById("reconnect");
9
10
  var disconnectBtn = document.getElementById("disconnect");
11
+ var connectionStateEl = document.getElementById("connectionState");
12
+ var tokenStateEl = document.getElementById("tokenState");
13
+ var reconnectStateEl = document.getElementById("reconnectState");
14
+ var connectionUrlEl = document.getElementById("connectionUrl");
15
+ var lastErrorEl = document.getElementById("lastError");
16
+ var lastBindingUpdateEl = document.getElementById("lastBindingUpdate");
17
+ var extensionVersionEl = document.getElementById("extensionVersion");
18
+ var sessionSummaryEl = document.getElementById("sessionSummary");
19
+ var sessionListEl = document.getElementById("sessionList");
20
+ var latestState = null;
10
21
  function setStatus(text, bad = false) {
11
22
  statusEl.textContent = text;
12
23
  statusEl.style.color = bad ? "#dc2626" : "#0f172a";
13
24
  }
25
+ function formatTimeAgo(at) {
26
+ if (typeof at !== "number") {
27
+ return "never";
28
+ }
29
+ const deltaSeconds = Math.max(0, Math.round((Date.now() - at) / 1e3));
30
+ if (deltaSeconds < 5) {
31
+ return "just now";
32
+ }
33
+ if (deltaSeconds < 60) {
34
+ return `${deltaSeconds}s ago`;
35
+ }
36
+ const deltaMinutes = Math.round(deltaSeconds / 60);
37
+ if (deltaMinutes < 60) {
38
+ return `${deltaMinutes}m ago`;
39
+ }
40
+ const deltaHours = Math.round(deltaMinutes / 60);
41
+ return `${deltaHours}h ago`;
42
+ }
43
+ function renderSessionBindings(state) {
44
+ sessionSummaryEl.textContent = `${state.count} sessions, ${state.attachedCount} attached, ${state.tabCount} tabs, ${state.detachedCount} detached`;
45
+ sessionListEl.replaceChildren();
46
+ for (const item of state.items) {
47
+ const li = document.createElement("li");
48
+ const location = item.windowId === null ? "no window" : `window ${item.windowId}`;
49
+ const active = item.activeTabId === null ? "no active tab" : `active ${item.activeTabId}`;
50
+ li.textContent = `${item.label}: ${item.tabCount} tabs, ${location}, ${active}`;
51
+ if (item.detached) {
52
+ li.style.color = "#b45309";
53
+ }
54
+ sessionListEl.appendChild(li);
55
+ }
56
+ }
57
+ function renderConnectionDetails(state) {
58
+ connectionStateEl.textContent = state.connectionState;
59
+ tokenStateEl.textContent = state.hasToken ? "configured" : "missing";
60
+ connectionUrlEl.textContent = state.wsUrl;
61
+ extensionVersionEl.textContent = state.extensionVersion;
62
+ if (state.manualDisconnect) {
63
+ reconnectStateEl.textContent = "manual disconnect";
64
+ } else if (typeof state.nextReconnectInMs === "number") {
65
+ const seconds = Math.max(0, Math.ceil(state.nextReconnectInMs / 100) / 10);
66
+ reconnectStateEl.textContent = `attempt ${state.reconnectAttempt}, retry in ${seconds}s`;
67
+ } else if (state.connected) {
68
+ reconnectStateEl.textContent = "connected";
69
+ } else {
70
+ reconnectStateEl.textContent = "idle";
71
+ }
72
+ if (state.lastError) {
73
+ const context = state.lastErrorContext ? `${state.lastErrorContext}: ` : "";
74
+ lastErrorEl.textContent = `${context}${state.lastError} (${formatTimeAgo(state.lastErrorAt)})`;
75
+ } else {
76
+ lastErrorEl.textContent = "none";
77
+ }
78
+ if (state.lastBindingUpdateReason) {
79
+ lastBindingUpdateEl.textContent = `${state.lastBindingUpdateReason} (${formatTimeAgo(state.lastBindingUpdateAt)})`;
80
+ } else {
81
+ lastBindingUpdateEl.textContent = "none";
82
+ }
83
+ }
14
84
  async function refreshState() {
15
85
  const state = await chrome.runtime.sendMessage({ type: "bak.getState" });
16
86
  if (state.ok) {
87
+ latestState = state;
17
88
  portInput.value = String(state.port);
18
89
  debugRichTextInput.checked = Boolean(state.debugRichText);
90
+ renderConnectionDetails(state);
91
+ renderSessionBindings(state.sessionBindings);
19
92
  if (state.connected) {
20
93
  setStatus("Connected to bak CLI");
94
+ } else if (state.connectionState === "missing-token") {
95
+ setStatus("Pair token is required", true);
96
+ } else if (state.connectionState === "manual") {
97
+ setStatus("Disconnected manually");
98
+ } else if (state.connectionState === "reconnecting") {
99
+ setStatus("Reconnecting to bak CLI", true);
21
100
  } else if (state.lastError) {
22
101
  setStatus(`Disconnected: ${state.lastError}`, true);
23
102
  } else {
@@ -28,7 +107,7 @@
28
107
  saveBtn.addEventListener("click", async () => {
29
108
  const token = tokenInput.value.trim();
30
109
  const port = Number.parseInt(portInput.value.trim(), 10);
31
- if (!token) {
110
+ if (!token && latestState?.hasToken !== true) {
32
111
  setStatus("Pair token is required", true);
33
112
  return;
34
113
  }
@@ -38,10 +117,15 @@
38
117
  }
39
118
  await chrome.runtime.sendMessage({
40
119
  type: "bak.updateConfig",
41
- token,
120
+ ...token ? { token } : {},
42
121
  port,
43
122
  debugRichText: debugRichTextInput.checked
44
123
  });
124
+ tokenInput.value = "";
125
+ await refreshState();
126
+ });
127
+ reconnectBtn.addEventListener("click", async () => {
128
+ await chrome.runtime.sendMessage({ type: "bak.reconnectNow" });
45
129
  await refreshState();
46
130
  });
47
131
  disconnectBtn.addEventListener("click", async () => {
@@ -49,4 +133,10 @@
49
133
  await refreshState();
50
134
  });
51
135
  void refreshState();
136
+ var refreshInterval = window.setInterval(() => {
137
+ void refreshState();
138
+ }, 1e3);
139
+ window.addEventListener("unload", () => {
140
+ window.clearInterval(refreshInterval);
141
+ });
52
142
  })();
package/dist/popup.html CHANGED
@@ -70,9 +70,50 @@
70
70
  background: #e2e8f0;
71
71
  color: #0f172a;
72
72
  }
73
+ #reconnect {
74
+ background: #dbeafe;
75
+ color: #1d4ed8;
76
+ }
73
77
  #status {
74
78
  margin-top: 10px;
75
79
  font-size: 12px;
80
+ font-weight: 600;
81
+ }
82
+ .panel {
83
+ margin-top: 12px;
84
+ padding: 10px;
85
+ border: 1px solid #cbd5e1;
86
+ border-radius: 8px;
87
+ background: rgba(255, 255, 255, 0.8);
88
+ }
89
+ .panel h2 {
90
+ margin: 0 0 8px;
91
+ font-size: 12px;
92
+ }
93
+ .meta-grid {
94
+ display: grid;
95
+ grid-template-columns: auto 1fr;
96
+ gap: 6px 10px;
97
+ font-size: 11px;
98
+ }
99
+ .meta-grid dt {
100
+ color: #475569;
101
+ }
102
+ .meta-grid dd {
103
+ margin: 0;
104
+ color: #0f172a;
105
+ word-break: break-word;
106
+ }
107
+ #sessionList {
108
+ margin: 0;
109
+ padding-left: 16px;
110
+ font-size: 11px;
111
+ color: #0f172a;
112
+ }
113
+ #sessionList:empty::before {
114
+ content: "No tracked sessions";
115
+ color: #64748b;
116
+ margin-left: -16px;
76
117
  }
77
118
  .hint {
78
119
  margin-top: 10px;
@@ -85,7 +126,7 @@
85
126
  <h1>Browser Agent Kit</h1>
86
127
  <label>
87
128
  Pair token
88
- <input id="token" placeholder="paste token from `bak pair`" />
129
+ <input id="token" placeholder="paste token from `bak pair` or leave blank to keep the saved token" />
89
130
  </label>
90
131
  <label>
91
132
  CLI port
@@ -97,9 +138,39 @@
97
138
  </label>
98
139
  <div class="row">
99
140
  <button id="save">Save & Connect</button>
141
+ <button id="reconnect">Reconnect</button>
142
+ </div>
143
+ <div class="row">
100
144
  <button id="disconnect">Disconnect</button>
101
145
  </div>
102
146
  <div id="status">Checking...</div>
147
+ <div class="panel">
148
+ <h2>Connection</h2>
149
+ <dl class="meta-grid">
150
+ <dt>State</dt>
151
+ <dd id="connectionState">-</dd>
152
+ <dt>Token</dt>
153
+ <dd id="tokenState">-</dd>
154
+ <dt>Reconnect</dt>
155
+ <dd id="reconnectState">-</dd>
156
+ <dt>CLI URL</dt>
157
+ <dd id="connectionUrl">-</dd>
158
+ <dt>Last error</dt>
159
+ <dd id="lastError">-</dd>
160
+ <dt>Last binding</dt>
161
+ <dd id="lastBindingUpdate">-</dd>
162
+ <dt>Extension</dt>
163
+ <dd id="extensionVersion">-</dd>
164
+ </dl>
165
+ </div>
166
+ <div class="panel">
167
+ <h2>Sessions</h2>
168
+ <dl class="meta-grid">
169
+ <dt>Tracked</dt>
170
+ <dd id="sessionSummary">-</dd>
171
+ </dl>
172
+ <ul id="sessionList"></ul>
173
+ </div>
103
174
  <div class="hint">Extension only connects to ws://127.0.0.1</div>
104
175
  <script src="./popup.global.js"></script>
105
176
  </body>
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@flrande/bak-extension",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@flrande/bak-protocol": "0.6.4"
6
+ "@flrande/bak-protocol": "0.6.6"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@types/chrome": "^0.1.14",