leapfrog-mcp 0.6.8 → 0.6.9
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/dist/session-manager.js +21 -7
- package/dist/tile-manager.d.ts +13 -0
- package/dist/tile-manager.js +33 -1
- package/package.json +1 -1
package/dist/session-manager.js
CHANGED
|
@@ -8,7 +8,7 @@ import { generateFingerprint } from "./humanize-fingerprint.js";
|
|
|
8
8
|
import { isHumanizeEnabled } from "./humanize-utils.js";
|
|
9
9
|
import { CdpConnector } from "./cdp-connector.js";
|
|
10
10
|
import { installSSRFRouteGuard } from "./ssrf.js";
|
|
11
|
-
import { tileManager } from "./tile-manager.js";
|
|
11
|
+
import { tileManager, TileManager } from "./tile-manager.js";
|
|
12
12
|
import * as fs from "fs/promises";
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
import * as os from "os";
|
|
@@ -565,13 +565,27 @@ export class SessionManager {
|
|
|
565
565
|
// Record the screen assignment so reflowAll keeps it on this screen.
|
|
566
566
|
if (isHeaded && !cdpConnected && detectedScreen) {
|
|
567
567
|
tileManager.assignSessionScreen(id, detectedScreen);
|
|
568
|
+
// Lock viewport for sessions with explicitly-set viewport so reflow
|
|
569
|
+
// doesn't override it. Unlocked sessions get dynamic viewport sync.
|
|
570
|
+
if (opts?.viewport) {
|
|
571
|
+
tileManager.lockViewport(id);
|
|
572
|
+
}
|
|
573
|
+
const initialBounds = {
|
|
574
|
+
x: detectedScreen.x,
|
|
575
|
+
y: detectedScreen.y,
|
|
576
|
+
width: Math.min(1280, detectedScreen.width),
|
|
577
|
+
height: Math.min(720, detectedScreen.height),
|
|
578
|
+
};
|
|
568
579
|
try {
|
|
569
|
-
await tileManager.positionWindowWithBounds(page, id,
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
580
|
+
await tileManager.positionWindowWithBounds(page, id, initialBounds);
|
|
581
|
+
// Dynamic viewport sync on initial placement
|
|
582
|
+
if (!opts?.viewport) {
|
|
583
|
+
const viewport = TileManager.calculateViewportFromBounds(initialBounds);
|
|
584
|
+
try {
|
|
585
|
+
await page.setViewportSize(viewport);
|
|
586
|
+
}
|
|
587
|
+
catch { }
|
|
588
|
+
}
|
|
575
589
|
}
|
|
576
590
|
catch (e) {
|
|
577
591
|
logger.warn("tile.position_failed", { error: e.message });
|
package/dist/tile-manager.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ declare class TileManager {
|
|
|
30
30
|
private windowIds;
|
|
31
31
|
/** Per-session screen assignment — windows stay on the screen where they were created. */
|
|
32
32
|
private sessionScreens;
|
|
33
|
+
/** Sessions with explicitly-set viewports — reflow won't override these. */
|
|
34
|
+
private viewportLocked;
|
|
35
|
+
/** Chrome UI height (tabs, address bar, bookmarks). Subtracted from tile height to get content area. */
|
|
36
|
+
static CHROME_HEIGHT: number;
|
|
33
37
|
configure(opts: {
|
|
34
38
|
layout: TileLayout;
|
|
35
39
|
padding: number;
|
|
@@ -79,6 +83,15 @@ declare class TileManager {
|
|
|
79
83
|
globalIndex: number;
|
|
80
84
|
}): string[];
|
|
81
85
|
positionWindow(page: Page, sessionId: string): Promise<void>;
|
|
86
|
+
/** Calculate the page viewport that fits inside a window of the given bounds. */
|
|
87
|
+
static calculateViewportFromBounds(bounds: TileBounds): {
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
};
|
|
91
|
+
/** Lock a session's viewport so reflow won't override an explicitly-set viewport. */
|
|
92
|
+
lockViewport(sessionId: string): void;
|
|
93
|
+
/** Check if a session's viewport is locked. */
|
|
94
|
+
isViewportLocked(sessionId: string): boolean;
|
|
82
95
|
/** Record which screen a session was placed on so reflows keep it there. */
|
|
83
96
|
assignSessionScreen(sessionId: string, screen: ScreenWorkArea): void;
|
|
84
97
|
/** Get the screen assigned to a session, or the current global screen. */
|
package/dist/tile-manager.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Opt-in via LEAP_TILE=true|grid|master env var.
|
|
5
5
|
//
|
|
6
6
|
// Key design:
|
|
7
|
-
// - Viewport
|
|
7
|
+
// - Viewport auto-syncs to tile size during reflow (dynamic viewport)
|
|
8
8
|
// - Screen detection via page.evaluate() on first headed session, cached
|
|
9
9
|
// - Accounts for macOS menu bar, Dock, and work area offset
|
|
10
10
|
// - Launch-time positioning via --window-position/--window-size Chrome args
|
|
@@ -23,6 +23,10 @@ class TileManager {
|
|
|
23
23
|
windowIds = new Map();
|
|
24
24
|
/** Per-session screen assignment — windows stay on the screen where they were created. */
|
|
25
25
|
sessionScreens = new Map();
|
|
26
|
+
/** Sessions with explicitly-set viewports — reflow won't override these. */
|
|
27
|
+
viewportLocked = new Set();
|
|
28
|
+
/** Chrome UI height (tabs, address bar, bookmarks). Subtracted from tile height to get content area. */
|
|
29
|
+
static CHROME_HEIGHT = process.platform === "darwin" ? 72 : 85;
|
|
26
30
|
// ── Configuration ──────────────────────────────────────────────────
|
|
27
31
|
configure(opts) {
|
|
28
32
|
this.enabled = true;
|
|
@@ -317,6 +321,21 @@ if (found) { found; } else {
|
|
|
317
321
|
// This is a low-level primitive; reflowAll calls it with correct bounds.
|
|
318
322
|
logger.debug("tile.position_window", { sessionId });
|
|
319
323
|
}
|
|
324
|
+
/** Calculate the page viewport that fits inside a window of the given bounds. */
|
|
325
|
+
static calculateViewportFromBounds(bounds) {
|
|
326
|
+
return {
|
|
327
|
+
width: Math.max(bounds.width, 400),
|
|
328
|
+
height: Math.max(bounds.height - TileManager.CHROME_HEIGHT, 200),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/** Lock a session's viewport so reflow won't override an explicitly-set viewport. */
|
|
332
|
+
lockViewport(sessionId) {
|
|
333
|
+
this.viewportLocked.add(sessionId);
|
|
334
|
+
}
|
|
335
|
+
/** Check if a session's viewport is locked. */
|
|
336
|
+
isViewportLocked(sessionId) {
|
|
337
|
+
return this.viewportLocked.has(sessionId);
|
|
338
|
+
}
|
|
320
339
|
/** Record which screen a session was placed on so reflows keep it there. */
|
|
321
340
|
assignSessionScreen(sessionId, screen) {
|
|
322
341
|
this.sessionScreens.set(sessionId, screen);
|
|
@@ -386,6 +405,18 @@ if (found) { found; } else {
|
|
|
386
405
|
if (!page || page.isClosed())
|
|
387
406
|
return;
|
|
388
407
|
await this.positionWindowWithBounds(page, id, bounds);
|
|
408
|
+
// Dynamic viewport sync — resize page viewport to match tile content area.
|
|
409
|
+
// Skip sessions where user explicitly set a viewport (viewport-locked).
|
|
410
|
+
if (!this.viewportLocked.has(id)) {
|
|
411
|
+
const viewport = TileManager.calculateViewportFromBounds(bounds);
|
|
412
|
+
try {
|
|
413
|
+
await page.setViewportSize(viewport);
|
|
414
|
+
logger.debug("tile.viewport_synced", { id, ...viewport });
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// Viewport sync is non-fatal
|
|
418
|
+
}
|
|
419
|
+
}
|
|
389
420
|
});
|
|
390
421
|
}));
|
|
391
422
|
// Bring all windows to front after positioning
|
|
@@ -416,6 +447,7 @@ if (found) { found; } else {
|
|
|
416
447
|
removeSession(sessionId) {
|
|
417
448
|
this.windowIds.delete(sessionId);
|
|
418
449
|
this.sessionScreens.delete(sessionId);
|
|
450
|
+
this.viewportLocked.delete(sessionId);
|
|
419
451
|
}
|
|
420
452
|
}
|
|
421
453
|
export const tileManager = new TileManager();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leapfrog-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "Multi-session browser MCP for AI agents — 36 tools, stealth, persistent auth, code-first scripts, API sniffer, agent intelligence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|