@agent360/browser-mcp 1.28.1 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,7 +127,7 @@ The pattern: **anything you would do yourself in a browser, on a site you are al
127
127
  | | Browser MCP | Playwright MCP | BrowserMCP.io |
128
128
  |---|---|---|---|
129
129
  | **Browser** | Your real Chrome | Headless (new session) | Your real Chrome |
130
- | **Maintained** | Actively — latest release v1.25.0 (2026-07-24) | Actively (Microsoft) | Last commit Apr 2025 |
130
+ | **Maintained** | Actively — latest release v1.29.0 (2026-09-07) | Actively (Microsoft) | Last commit Apr 2025 |
131
131
  | **Logins/cookies** | Already authenticated | Must log in every time | Already authenticated |
132
132
  | **Multi-session** | 20 concurrent sessions with color-coded tab groups | Single session | Single session |
133
133
  | **Human-in-the-loop** | `browser_ask_user` — 2FA, CAPTCHA, credential input | None | None |
@@ -211,6 +211,8 @@ async function evictOldestTabs(session, justAddedTabId) {
211
211
  async function addTabToSession(port, tabId) {
212
212
  const session = getSession(port);
213
213
  session.tabIds.add(tabId);
214
+ // Sessionen lever igen — aflys en eventuel port-frigivelse (se tabs.onRemoved).
215
+ chrome.alarms.clear(`frigiv-${port}`).catch(() => {});
214
216
 
215
217
  // LRU eviction: når sessionen overstiger cap, luk de ældste tabs.
216
218
  if (session.tabIds.size > MAX_TABS_PER_SESSION) {
@@ -247,6 +249,29 @@ async function releaseSession(port) {
247
249
  const session = sessions.get(port);
248
250
  if (!session) return;
249
251
 
252
+ // ── Bad vi selv om frigivelsen? (FUNDET AF REVIEW 7/9) ────────────────────
253
+ //
254
+ // Vejen fra "sessionen er tom" til denne funktion gaar over mindst tre hop:
255
+ // sendMessage -> offscreen sender terminate og lukker WS -> ws.onclose ->
256
+ // session_disconnect. Aabner agenten en fane i de ~50 ms undervejs, lukkede
257
+ // oprydningen herunder DEN fane — paa grundlag af en beslutning der blev truffet
258
+ // foer fanen fandtes. Det er praecis "agenten holder pause og genoptager"-
259
+ // scenariet frigivelsen er bygget til at understoette.
260
+ //
261
+ // En UVENTET afbrydelse (chatten er vaek) skal stadig lukke fanerne — derfor
262
+ // skelnes der, i stedet for bare at tjekke om sessionen er tom.
263
+ if (frivilligtFrigivet.delete(port)) {
264
+ if (session.tabIds.size) {
265
+ // Sessionen arbejder igen. Behold den, saa naeste binding kan adoptere den
266
+ // (adoptOrphanedSession finder donorer med samme pid OG faner).
267
+ persistSessions();
268
+ return;
269
+ }
270
+ sessions.delete(port);
271
+ persistSessions();
272
+ return;
273
+ }
274
+
250
275
  // Detach debugger + close all session tabs
251
276
  const tabIds = [...session.tabIds];
252
277
  for (const tabId of tabIds) {
@@ -401,7 +426,13 @@ async function debuggerAttach(tabId) {
401
426
  lastMsg = 'attach resolved but Chrome shows tab not attached (ghost — page likely mid-reload)';
402
427
  try { await chrome.debugger.detach({ tabId }); } catch {}
403
428
  } catch (e) {
404
- if (e.message?.includes('Already attached')) {
429
+ // MAALT 31/8 af den nye udvidelses-test: her stod `includes('Already attached')`
430
+ // med stort A. Chromes faktiske besked er "Another debugger is already attached
431
+ // to the tab with id: N" — med lille. Tjekket ramte ALDRIG. Resultat: naar en
432
+ // anden debugger havde fanen (DevTools aabent, en anden udvidelse), blev det
433
+ // behandlet som en fejl, proevet tre gange, og kastet — i stedet for bare at
434
+ // bruge den session der allerede fandtes.
435
+ if (/already attached/i.test(e.message || '')) {
405
436
  // Chrome side has session — sync local cache
406
437
  debuggerAttached.add(tabId);
407
438
  return;
@@ -504,6 +535,10 @@ async function cdpSend(tabId, method, params = {}) {
504
535
  // Faner agenten selv lukkede via close_tab. En tom session betyder kun "arbejdet er slut"
505
536
  // hvis det var BRUGEREN der lukkede den sidste fane.
506
537
  const agentLukkedeFaner = new Set();
538
+ // Porte hvor VI selv har bedt serveren slippe porten, fordi sessionen var tom.
539
+ // Skelnen betyder alt i releaseSession: en frivillig frigivelse maa aldrig lukke
540
+ // faner, mens en uventet afbrydelse (chatten er vaek) netop skal rydde op.
541
+ const frivilligtFrigivet = new Set();
507
542
 
508
543
  // ── Armerede dialog-haandterere, pr. fane ──────────────────────────────────────
509
544
  // MAALT 22/8 af flowtesten: handle_dialog var ubrugelig som den var skrevet. Den
@@ -546,7 +581,25 @@ chrome.tabs.onRemoved.addListener((tabId) => {
546
581
  if (session.tabIds.size === 0 && !lukketAfAgenten) {
547
582
  // Last tab closed — tell offscreen to terminate the MCP server.
548
583
  // Resulting WS-close triggers the existing session_disconnect → releaseSession path.
584
+ frivilligtFrigivet.add(port);
549
585
  chrome.runtime.sendMessage({ type: 'terminate_mcp_session', port }).catch(() => {});
586
+ } else if (session.tabIds.size === 0) {
587
+ // ── Agenten lukkede selv sin sidste fane (MAALT 7/9-2026) ────────────────
588
+ // Her stod der intet, og det var med vilje: en agent der lukker en fane midt i
589
+ // et forloeb skal ikke miste browseren. Men serverens EGEN instruks siger til
590
+ // hver eneste agent: "ALWAYS close tabs when done". Hver velopdragen chat endte
591
+ // altsaa med at holde sin port til 4-timers-tomgangen udloeb — den dokumenterede
592
+ // god-praksis slog oprydningen ihjel, og 20 porte kunne staa optaget af chats
593
+ // der for laengst var faerdige.
594
+ //
595
+ // Nu: fem minutters henstand. Kommer der en ny fane inden da, aflyses den
596
+ // (se getSessionTab). Sker der intet, bedes serveren slippe porten — den DOER
597
+ // ikke, saa chatten kan hente browseren tilbage naar som helst.
598
+ //
599
+ // chrome.alarms og ikke setTimeout: en MV3-service-worker suspenderes, og en
600
+ // timer ville forsvinde med den.
601
+ chrome.alarms.create(`frigiv-${port}`, { delayInMinutes: 5 });
602
+ persistSessions();
550
603
  } else {
551
604
  persistSessions();
552
605
  }
@@ -3524,9 +3577,24 @@ async function dispatch(port, method, params) {
3524
3577
  throw new Error(`Tab ${params.tab_id} does not belong to this session (${session.label})`);
3525
3578
  }
3526
3579
  const tab = await chrome.tabs.update(params.tab_id, { active: true });
3580
+ // Gør ogsaa VINDUET forrest. Uden det bliver fanen aktiv inde i sit vindue —
3581
+ // document.hasFocus() bliver sand — men document.visibilityState forbliver
3582
+ // 'hidden' fordi vinduet ligger bagved. Chrome struber timere i skjulte
3583
+ // faner, saa Angular-apps (Google Ads, GA4, Search Console) renderer aldrig
3584
+ // faerdigt: man laeser en halvt bygget side og drager forkerte konklusioner.
3585
+ // Kostede to opgaver og en forkert konklusion 31/8-2026.
3586
+ let vinduesFokus = null;
3587
+ try {
3588
+ await chrome.windows.update(tab.windowId, { focused: true });
3589
+ vinduesFokus = true;
3590
+ } catch (e) {
3591
+ // Vinduet kan vaere lukket eller paa et andet Space. Fanen er stadig
3592
+ // aktiv; vi siger bare aerligt at synligheden ikke kunne sikres.
3593
+ vinduesFokus = false;
3594
+ }
3527
3595
  session.activeTabId = tab.id;
3528
3596
  persistSessions();
3529
- return { id: tab.id, url: tab.url, title: tab.title };
3597
+ return { id: tab.id, url: tab.url, title: tab.title, windowFocused: vinduesFokus };
3530
3598
  }
3531
3599
 
3532
3600
  case 'close_tab': {
@@ -3898,4 +3966,33 @@ chrome.alarms.onAlarm.addListener((alarm) => {
3898
3966
  if (alarm.name === 'ensure-offscreen') {
3899
3967
  ensureOffscreen().catch(console.error);
3900
3968
  }
3969
+ if (alarm.name.startsWith('frigiv-')) {
3970
+ const port = Number(alarm.name.slice('frigiv-'.length));
3971
+ // ── Hvorfor lageret laeses DIREKTE og ikke via restoreSessions() ─────────
3972
+ //
3973
+ // FUNDET AF REVIEW 7/9. Her stod `restoreSessions().then(...)`, og kommentaren
3974
+ // sagde at det var vaernet mod en genstartet service-worker. Den gjorde det
3975
+ // modsatte: `restoreSessions()` gendanner kun sessioner der har GYLDIGE FANER
3976
+ // (`if (validTabIds.size > 0)`) — og en tom session er praecis den her alarm
3977
+ // handler om. En MV3-worker suspenderes efter ~30 sekunder; fristen er paa fem
3978
+ // minutter, saa workeren er naesten altid frisk naar alarmen fyrer. Sessionen
3979
+ // blev derfor aldrig fundet, `if (!s) return` ramte, og porten blev holdt til
3980
+ // 4-timers-tomgangen — altsaa praecis den fejl frigivelsen skulle fjerne.
3981
+ //
3982
+ // restoreSessions() har god grund til ikke at genoplive doede sessioner (andre
3983
+ // kaldere vil ikke arve faner der ikke findes). Derfor rettes det HER.
3984
+ (async () => {
3985
+ const iHukommelsen = sessions.get(port);
3986
+ if (iHukommelsen) {
3987
+ if (iHukommelsen.tabIds.size > 0) return; // den arbejder igen — lad den vaere
3988
+ } else {
3989
+ const { sessions: gemte } = await chrome.storage.local.get({ sessions: {} });
3990
+ const gemt = gemte[String(port)];
3991
+ if (!gemt) return; // sessionen er reelt vaek
3992
+ if ((gemt.tabIds || []).length > 0) return; // den arbejder igen
3993
+ }
3994
+ frivilligtFrigivet.add(port);
3995
+ chrome.runtime.sendMessage({ type: 'terminate_mcp_session', port }).catch(() => {});
3996
+ })().catch(() => {});
3997
+ }
3901
3998
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Agent360 Browser MCP",
4
- "version": "1.28.1",
4
+ "version": "1.29.0",
5
5
  "description": "Give Claude Code your real logged-in Chrome. Two parts: this extension + one npx command. Multi-session, local, MIT.",
6
6
  "permissions": [
7
7
  "tabs",
package/index.js CHANGED
@@ -39,8 +39,11 @@ const repoDir = dirname(__dirname); // parent of mcp-server/
39
39
  // `npx @agent360/browser-mcp@latest` opdaterer allerede serveren; blokken var overfloedig.
40
40
  let extensionUpdated = false;
41
41
 
42
- const BASE_PORT = 9876;
43
- const MAX_PORT = 9895; // 20 ports instead of 10 zombies die within 5s via parent check
42
+ // Spaendet kan flyttes med env — ellers ville en test af port-udsultning skulle
43
+ // beslaglaegge de RIGTIGE porte og dermed sulte brugerens oevrige chats imens.
44
+ // Uden env er vaerdierne uaendrede. (Samme moenster som BROWSER_MCP_EXTENSION_ID.)
45
+ const BASE_PORT = Number(process.env.BROWSER_MCP_BASE_PORT) || 9876;
46
+ const MAX_PORT = Number(process.env.BROWSER_MCP_MAX_PORT) || 9895; // 20 ports instead of 10 — zombies die within 5s via parent check
44
47
 
45
48
  // ── Extension connections ───────────────────────────────────────────────────
46
49
  // FEJL MAALT 21/8: her stod `let extensionSocket = null`, og hver ny forbindelse
@@ -58,6 +61,7 @@ const connections = new Set(); // { ws, seq, extensionId, version, name, since }
58
61
  let connSeq = 0;
59
62
  let activePort = null;
60
63
  let alleePorteOptaget = false; // hele spaendet i brug — se createWSS
64
+ let bindFejl = null; // bind fejlede af en ANDEN grund end optaget port
61
65
 
62
66
  function cmpVersion(a, b) {
63
67
  const pa = String(a || '0.0.0').split('.').map(n => parseInt(n, 10) || 0);
@@ -147,6 +151,22 @@ const pending = new Map();
147
151
  // Timers hoisted to module scope so gracefulShutdown can clear them deterministically.
148
152
  let heartbeat = null;
149
153
  let parentCheck = null;
154
+ let tomgangsvagt = null;
155
+
156
+ // ── 4-timers-tomgangen bor paa modul-niveau, ikke i hjerteslaget ────────────
157
+ //
158
+ // FUNDET AF REVIEW 7/9. Tjekket laa INDE i `heartbeat`, som kun findes mens serveren
159
+ // har en port — og som `frigivPort` rydder. Da porten blev doven, betoed det at en
160
+ // proces der ALDRIG binder (en chat der ikke roerer browseren) heller aldrig faar
161
+ // sin tomgang tjekket. Det var den eneste vej hvor en browser-inaktiv server gav sine
162
+ // ~35 MB tilbage; med mange samtidige chats er det maalbart. Nu koerer vagten altid.
163
+ // Graensen staar som literal, ikke bag en konstant: `vagter.test.mjs` laeser tallet
164
+ // direkte ud af kilden for at haandhaeve at den aldrig bliver kort. En kort graense
165
+ // ville lade en aaben chat miste browseren permanent — vaerre end en port der staar
166
+ // optaget lidt for laenge.
167
+ tomgangsvagt = setInterval(() => {
168
+ if (Date.now() - lastActivity > 4 * 60 * 60 * 1000) gracefulShutdown('Idle timeout (4h)');
169
+ }, 60000);
150
170
 
151
171
  // ── WebSocket Server ───────────────────────────────────────────────────────
152
172
 
@@ -187,9 +207,19 @@ function createWSS(port = BASE_PORT) {
187
207
  // sendToExtension, saa agenten kan give brugeren den rigtige forklaring.
188
208
  alleePorteOptaget = true;
189
209
  process.stderr.write(`[MCP] All ports ${BASE_PORT}-${MAX_PORT} in use. Cannot start.\n`);
210
+ loesPortLoefte(false);
190
211
  }
191
212
  } else {
213
+ // FUNDET AF REVIEW 7/9. Her stod KUN stderr-linjen. Foer porten blev doven, var
214
+ // det harmloest: en opstartsfejl man kunne se i loggen. Nu venter `sikrePort()`
215
+ // paa et loefte der aldrig blev indfriet — og `sendToExtension` goer
216
+ // `await sikrePort()` UDEN timeout. Enhver anden bind-fejl end EADDRINUSE
217
+ // (EACCES paa en privilegeret port, EADDRNOTAVAIL, en restriktiv firewall)
218
+ // ville derfor faa hvert eneste browser-kald til at haenge tavst for evigt.
219
+ // At haenge uden besked er vaerre end den fejl vi rettede.
220
+ bindFejl = err.message;
192
221
  process.stderr.write(`[MCP] WebSocket error: ${err.message}\n`);
222
+ loesPortLoefte(false);
193
223
  }
194
224
  });
195
225
 
@@ -305,7 +335,11 @@ function createWSS(port = BASE_PORT) {
305
335
  process.stderr.write('[MCP] terminate ignoreret — kom fra en inaktiv udvidelses-forbindelse\n');
306
336
  return;
307
337
  }
308
- gracefulShutdown('Terminate signal from extension (last tab closed)');
338
+ // AENDRET 7/9: her stod `gracefulShutdown`. En chat der var faerdig med browseren
339
+ // mistede altsaa browseren HELT — og porten blev alligevel hverken frigivet hurtigt
340
+ // nok til andre (processen doede foerst efter oprydning) eller genvundet af chatten
341
+ // selv. Nu slippes kun porten; naeste browser-kald tager en ny (se sikrePort).
342
+ frigivPort('udvidelsen meldte: sidste fane lukket');
309
343
  return;
310
344
  }
311
345
 
@@ -359,22 +393,93 @@ function createWSS(port = BASE_PORT) {
359
393
  server.on('listening', () => {
360
394
  activePort = port;
361
395
  process.stderr.write(`[MCP] WebSocket server listening on ws://127.0.0.1:${port}\n`);
396
+ loesPortLoefte(true);
362
397
  });
363
398
 
364
399
  // Heartbeat + idle timeout (4 hours) — hoisted to module scope so gracefulShutdown can clear it
400
+ // Ryddes foerst: porten kan bindes flere gange i samme proces (se sikrePort), og uden
401
+ // det her ville hver ny binding efterlade en ekstra timer der aldrig blev stoppet.
402
+ if (heartbeat) clearInterval(heartbeat);
365
403
  heartbeat = setInterval(() => {
366
404
  for (const c of liveConnections()) c.ws.ping();
367
- if (Date.now() - lastActivity > 4 * 60 * 60 * 1000) {
368
- gracefulShutdown('Idle timeout (4h)');
369
- }
370
405
  }, 20000);
371
406
  }
372
407
 
373
- createWSS();
408
+ // ── Porten tages ved BRUG, ikke ved opstart ─────────────────────────────────
409
+ //
410
+ // MAALT 7/9-2026 paa Gustavs maskine: 37 koerende servere, 20 porte i spaendet, 17 chats
411
+ // helt uden browser. Hver Claude Code-chat starter en server ved opstart — ogsaa de mange
412
+ // chats der aldrig roerer browseren — og her stod `createWSS()` paa modul-niveau. Porten
413
+ // blev altsaa reserveret af en chat der maaske aldrig fik brug for den, og holdt indtil
414
+ // chatten doede eller 4-timers-tomgangen udloeb.
415
+ //
416
+ // Konsekvensen var ensidigt slem: chat nr. 21 fik `alleePorteOptaget = true` ÉN gang og
417
+ // proevede aldrig igen — dens browser var doed hele chattens levetid, selv naar en port
418
+ // blev fri et minut senere.
419
+ //
420
+ // Nu bindes porten foerste gang et vaerktoej faktisk skal bruge udvidelsen, og HVERT kald
421
+ // proever igen hvis det forrige ikke fik en. Udvidelsen genscanner hele spaendet hvert
422
+ // 2. sekund (offscreen.js), saa en port der aabnes sent bliver fundet af sig selv.
423
+ //
424
+ // Bemaerk hvorfor det ikke er en 5-minutters timer der draeber processen: en chat der
425
+ // foerst skal bruge browseren efter en halv time ville saa staa uden. Processen lever
426
+ // videre — det er kun PORTEN der ikke holdes reserveret til noget der ikke sker.
427
+ let portResolver = null;
428
+ let bindLoefte = null;
429
+
430
+ function loesPortLoefte(fik) {
431
+ if (!portResolver) return;
432
+ const r = portResolver;
433
+ portResolver = null;
434
+ bindLoefte = null;
435
+ r(fik);
436
+ }
437
+
438
+ // Slip porten, men BLIV I LIVE. Forskellen er hele pointen: lukkede vi processen ned,
439
+ // ville en chat der er faerdig med browseren kl. 10 og skal bruge den igen kl. 10:40 staa
440
+ // uden — og Claude Code genstarter ikke en MCP-server midt i en samtale. Processen koster
441
+ // ~35 MB og ingen port; det er porten der er den knappe ressource.
442
+ function frigivPort(grund) {
443
+ if (activePort === null) return;
444
+ process.stderr.write(`[MCP] frigiver port ${activePort} — ${grund}\n`);
445
+ if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }
446
+ const gammel = wss;
447
+ wss = null;
448
+ activePort = null;
449
+ laastForbindelse = null;
450
+ harSendtKommando = false; // naeste binding vaelger udvidelse forfra
451
+ for (const c of connections) { try { c.ws.close(); } catch {} }
452
+ connections.clear();
453
+ try { gammel?.close(); } catch {}
454
+ }
455
+
456
+ function sikrePort() {
457
+ if (activePort !== null) return Promise.resolve(true);
458
+ if (bindLoefte) return bindLoefte; // en binding er allerede i gang
459
+ alleePorteOptaget = false; // hvert forsoeg starter paa en frisk
460
+ bindFejl = null;
461
+ bindLoefte = new Promise((res) => { portResolver = res; });
462
+ // Vagthund. F8 var ét konkret hul hvor loeftet aldrig blev indfriet; det her lukker
463
+ // KLASSEN. Emitter en fremtidig fejlsti hverken 'listening' eller 'error', svarer
464
+ // kaldet nu med en fejl i stedet for at haenge tavst for evigt. 10 s er rigeligt:
465
+ // en loopback-binding tager millisekunder, og hele spaendet naas paa under ét.
466
+ const vagthund = setTimeout(() => {
467
+ if (!portResolver) return;
468
+ bindFejl = bindFejl || 'bindingen svarede ikke inden for 10 sekunder';
469
+ process.stderr.write('[MCP] port-bindingen svarede aldrig — opgiver dette forsoeg\n');
470
+ loesPortLoefte(false);
471
+ }, 10000);
472
+ bindLoefte.finally(() => clearTimeout(vagthund));
473
+ createWSS();
474
+ return bindLoefte;
475
+ }
374
476
 
375
477
  // ── Send command to extension ───────────────────────────────────────────────
376
478
 
377
479
  async function sendToExtension(method, params = {}, timeoutMs = 30000, _retries = 5) {
480
+ // Skaf en port hvis vi ikke har en. Foerste kald binder; senere kald er en no-op.
481
+ // Fik vi ingen (hele spaendet optaget), proever naeste kald igen — derfor ingen kast her.
482
+ await sikrePort();
378
483
  // Retry if extension is temporarily disconnected (reconnects every 2s)
379
484
  const conn = activeConnection();
380
485
  if (!conn) {
@@ -385,12 +490,21 @@ async function sendToExtension(method, params = {}, timeoutMs = 30000, _retries
385
490
  // This is the other half of the two-part setup: the server is clearly running (it is
386
491
  // throwing this), so what is missing is the extension, Chrome itself, or the connection
387
492
  // between them. Say which, and where to get it — the agent relays this text to the user.
493
+ if (bindFejl) {
494
+ throw new Error(
495
+ `Serveren kunne ikke aabne en port paa 127.0.0.1 (${BASE_PORT}-${MAX_PORT}): ${bindFejl}\n` +
496
+ 'Det er IKKE Chrome eller udvidelsen — det er operativsystemet eller en firewall der ' +
497
+ 'afviser bindingen. Tjek om noget blokerer loopback-porte.\n' +
498
+ 'Sig praecis dét til brugeren. Sig IKKE at udvidelsen mangler.',
499
+ );
500
+ }
388
501
  if (alleePorteOptaget) {
389
502
  throw new Error(
390
- `Alle porte ${BASE_PORT}-${MAX_PORT} er optaget, saa denne server fik aldrig en port. ` +
503
+ `Alle porte ${BASE_PORT}-${MAX_PORT} er optaget lige nu, saa dette kald fik ingen port. ` +
391
504
  'Det er IKKE et problem med Chrome eller udvidelsen — de virker fint.\n' +
392
- `Du har ${MAX_PORT - BASE_PORT + 1} chats i gang der bruger browseren samtidig. ` +
393
- 'Luk en af dem pladsen frigives inden for faa sekunder — og genstart saa denne chat.\n' +
505
+ `${MAX_PORT - BASE_PORT + 1} andre chats bruger browseren i oejeblikket. ` +
506
+ 'Luk en af dem, eller vent til en bliver faerdig — og proev saa kommandoen igen. ' +
507
+ 'Denne chat skal IKKE genstartes: hvert kald proever selv at faa en port.\n' +
394
508
  'Sig praecis dét til brugeren. Sig IKKE at udvidelsen mangler.',
395
509
  );
396
510
  }
@@ -841,7 +955,11 @@ async function handleProvideFeedback(args) {
841
955
  'Det skal brugeren selv goere — chrome:// kan ikke styres herfra. Behold den nyeste.',
842
956
  );
843
957
  }
844
- if (!active) {
958
+ if (!active && activePort === null) {
959
+ // Ingen port taget endnu = browseren er ikke brugt i denne chat. Der er intet
960
+ // i stykker, og et fix-skridt her ville vaere en falsk alarm — se verdict 'idle'.
961
+ findings.push('Browseren er ikke taget i brug i denne chat endnu, saa der er ingen forbindelse at maale paa. Det er ikke en fejl.');
962
+ } else if (!active) {
845
963
  findings.push('Ingen Chrome-udvidelse er forbundet til denne MCP-server lige nu.');
846
964
  fix_steps.push('Tjek at Chrome koerer og at udvidelsen er slaaet til paa chrome://extensions, klik derefter paa ikonet → Reconnect.');
847
965
  }
@@ -872,9 +990,24 @@ async function handleProvideFeedback(args) {
872
990
  );
873
991
  }
874
992
 
993
+ // ── "ingen forbindelse" og "ingen port endnu" er IKKE det samme ────────────
994
+ //
995
+ // FUNDET AF REVIEW 7/9. Foer porten blev doven, bandt hver server ved opstart, saa
996
+ // udvidelsen var altid forbundet naar dette vaerktoej blev kaldt — og `!active`
997
+ // betoed derfor paalideligt "udvidelsen kan ikke naas". Nu binder en chat foerst en
998
+ // port naar den bruger browseren, saa en HELT SUND chat der ikke har roert den kan
999
+ // staa uden forbindelse. Uden det her skel fik den `disconnected` + fix_steps der
1000
+ // bad brugeren geninstallere — og INSTRUCTIONS beder agenten viderebringe dem.
1001
+ // Vi ville altsaa fortaelle folk at deres installation var i stykker, fordi vi selv
1002
+ // endnu ikke havde aabnet doeren.
1003
+ //
1004
+ // Loesningen er ikke at binde en port her (et diagnose-vaerktoej skal ikke aendre
1005
+ // tilstand for at kunne maale den) — det er at sige praecis hvad der er tilfaeldet.
1006
+ const ingenPortEndnu = activePort === null;
875
1007
  const verdict =
876
1008
  exts.length > 1 ? 'conflict'
877
1009
  : (serverOutdated || extOutdated) ? 'outdated'
1010
+ : (!active && ingenPortEndnu) ? 'idle'
878
1011
  : !active ? 'disconnected'
879
1012
  : (npmLatest === null ? 'unknown' : 'current');
880
1013
 
@@ -914,7 +1047,11 @@ async function handleProvideFeedback(args) {
914
1047
  `&title=${encodeURIComponent(issueTitle)}&body=${encodeURIComponent(issueBody)}`;
915
1048
 
916
1049
  const instruction =
917
- verdict === 'conflict' || verdict === 'outdated' || verdict === 'disconnected'
1050
+ verdict === 'idle'
1051
+ ? 'Browseren er ikke taget i brug i denne chat endnu, saa der er intet at diagnosticere paa forbindelsen — ' +
1052
+ 'det er IKKE en fejl i installationen, og du maa ikke sige det til brugeren. ' +
1053
+ 'Er der en aegte mangel, saa tilbyd submit_url som et klikbart link.'
1054
+ : verdict === 'conflict' || verdict === 'outdated' || verdict === 'disconnected'
918
1055
  ? 'Fortael brugeren hvad der blev fundet, og giv fix_steps som konkrete skridt. Proev derefter handlingen igen. ' +
919
1056
  'Del KUN submit_url hvis problemet stadig staar efter at fix_steps er fulgt — det er sandsynligvis installationen, ikke en fejl i Browser MCP.'
920
1057
  : 'Installationen er frisk, saa det her er sandsynligvis en aegte mangel eller fejl. Fortael brugeren kort hvad der ikke kunne lade sig goere, ' +
@@ -982,6 +1119,7 @@ function gracefulShutdown(reason, code = 0) {
982
1119
  // Stop timers so they can't re-enter gracefulShutdown
983
1120
  if (parentCheck) clearInterval(parentCheck);
984
1121
  if (heartbeat) clearInterval(heartbeat);
1122
+ if (tomgangsvagt) clearInterval(tomgangsvagt);
985
1123
 
986
1124
  // Close WS with explicit close-frame so extension's onclose handler fires
987
1125
  for (const c of liveConnections()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent360/browser-mcp",
3
- "version": "1.28.1",
3
+ "version": "1.29.0",
4
4
  "description": "Browser MCP — give your AI agent a real, logged-in Chrome: test authenticated apps, read 2FA codes from Gmail, scrape behind logins, solve CAPTCHAs. 40 tools, multi-session, human-in-the-loop.",
5
5
  "mcpName": "io.github.Agent360dk/browser-mcp",
6
6
  "type": "module",
@@ -64,11 +64,11 @@
64
64
  "ws": "^8.18.0"
65
65
  },
66
66
  "overrides": {
67
- "fast-uri": "^3.1.5",
67
+ "fast-uri": "^3.1.7",
68
68
  "hono": "^4.12.34",
69
69
  "ip-address": "^10.3.1",
70
70
  "@hono/node-server": "^1.19.15",
71
71
  "body-parser": "^2.2.3",
72
- "qs": "^6.15.2"
72
+ "qs": "^6.16.0"
73
73
  }
74
74
  }