Skip to content

Commit cdfe2a7

Browse files
committed
ensuring screenshots are deleted by mapping sessionids to the screenshot then deleting them after
1 parent e567228 commit cdfe2a7

File tree

4 files changed

+97
-2
lines changed

4 files changed

+97
-2
lines changed

src/mcp/resources.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,69 @@ export const RESOURCE_TEMPLATES = [];
1313
// Store screenshots in a map
1414
export const screenshots = new Map<string, string>();
1515

16+
// Track screenshots by session so we can purge them on session end
17+
// key: sessionId (internal/current session id), value: set of screenshot names
18+
const sessionIdToScreenshotNames = new Map<string, Set<string>>();
19+
20+
export function registerScreenshot(
21+
sessionId: string,
22+
name: string,
23+
base64: string,
24+
) {
25+
screenshots.set(name, base64);
26+
let set = sessionIdToScreenshotNames.get(sessionId);
27+
if (!set) {
28+
set = new Set();
29+
sessionIdToScreenshotNames.set(sessionId, set);
30+
}
31+
set.add(name);
32+
}
33+
34+
export function clearScreenshotsForSession(sessionId: string) {
35+
const set = sessionIdToScreenshotNames.get(sessionId);
36+
if (set) {
37+
for (const name of set) {
38+
screenshots.delete(name);
39+
}
40+
sessionIdToScreenshotNames.delete(sessionId);
41+
}
42+
}
43+
44+
export function clearAllScreenshots() {
45+
screenshots.clear();
46+
sessionIdToScreenshotNames.clear();
47+
}
48+
49+
/**
50+
* Retry wrapper for clearing screenshots for a session. Uses exponential backoff.
51+
* This protects against transient failures in any consumer of the map.
52+
*/
53+
export async function retryClearScreenshotsForSession(
54+
sessionId: string,
55+
options: { retries?: number; initialDelayMs?: number } = {},
56+
): Promise<void> {
57+
const retries = options.retries ?? 3;
58+
const initialDelayMs = options.initialDelayMs ?? 50;
59+
60+
let attempt = 0;
61+
let delay = initialDelayMs;
62+
// Attempt at least once
63+
while (true) {
64+
try {
65+
clearScreenshotsForSession(sessionId);
66+
return;
67+
} catch (err) {
68+
if (attempt >= retries) {
69+
// Give up
70+
throw err instanceof Error ? err : new Error(String(err));
71+
}
72+
await new Promise((r) => setTimeout(r, delay));
73+
attempt += 1;
74+
delay = Math.min(delay * 2, 1000);
75+
}
76+
}
77+
}
78+
1679
/**
1780
* Handle listing resources request
1881
* @returns A list of available resources including screenshots

src/sessionManager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Page, BrowserContext } from "@browserbasehq/stagehand";
22
import type { Config } from "../config.d.ts";
33
import type { Cookie } from "playwright-core";
44
import { createStagehandInstance } from "./stagehandStore.js";
5+
import { retryClearScreenshotsForSession } from "./mcp/resources.js";
56
import type { BrowserSession } from "./types/types.js";
67

78
// Global state for managing browser sessions
@@ -131,6 +132,13 @@ export async function createNewBrowserSession(
131132
);
132133
setActiveSessionId(defaultSessionId);
133134
}
135+
136+
// Purge any screenshots associated with both internal and Browserbase IDs
137+
retryClearScreenshotsForSession(newSessionId).catch(() => {});
138+
const bbId = browserbaseSessionId;
139+
if (bbId) {
140+
retryClearScreenshotsForSession(bbId).catch(() => {});
141+
}
134142
});
135143

136144
// Add cookies to the context if they are provided in the config
@@ -192,6 +200,12 @@ async function closeBrowserGracefully(
192200
process.stderr.write(
193201
`[SessionManager] Successfully closed Stagehand and browser for session: ${sessionIdToLog}\n`,
194202
);
203+
// After close, purge any screenshots associated with both internal and Browserbase IDs
204+
retryClearScreenshotsForSession(sessionIdToLog).catch(() => {});
205+
const bbId = session?.stagehand?.browserbaseSessionID;
206+
if (bbId) {
207+
retryClearScreenshotsForSession(bbId).catch(() => {});
208+
}
195209
} catch (closeError) {
196210
process.stderr.write(
197211
`[SessionManager] WARN - Error closing Stagehand for session ${sessionIdToLog}: ${
@@ -335,6 +349,9 @@ export async function cleanupSession(sessionId: string): Promise<void> {
335349
// Remove from browsers map
336350
browsers.delete(sessionId);
337351

352+
// Always purge screenshots for this (internal) session id
353+
await retryClearScreenshotsForSession(sessionId).catch(() => {});
354+
338355
// Clear default session reference if this was the default
339356
if (sessionId === defaultSessionId && defaultBrowserSession) {
340357
defaultBrowserSession = null;

src/stagehandStore.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { randomUUID } from "crypto";
22
import { Stagehand, Page } from "@browserbasehq/stagehand";
33
import { StagehandSession, CreateSessionParams } from "./types/types.js";
44
import type { Config } from "../config.d.ts";
5+
import { retryClearScreenshotsForSession } from "./mcp/resources.js";
56

67
// Store for all active sessions
78
const store = new Map<string, StagehandSession>();
@@ -110,6 +111,12 @@ export const create = async (
110111
const disconnectHandler = () => {
111112
process.stderr.write(`[StagehandStore] Session disconnected: ${id}\n`);
112113
store.delete(id);
114+
// Purge by internal store ID and Browserbase session ID
115+
retryClearScreenshotsForSession(id).catch(() => {});
116+
const bbId = session.metadata?.bbSessionId;
117+
if (bbId) {
118+
retryClearScreenshotsForSession(bbId).catch(() => {});
119+
}
113120
};
114121

115122
browser.on("disconnected", disconnectHandler);
@@ -158,6 +165,12 @@ export const remove = async (id: string): Promise<void> => {
158165

159166
await session.stagehand.close();
160167
process.stderr.write(`[StagehandStore] Session closed: ${id}\n`);
168+
// Purge by internal store ID and Browserbase session ID
169+
await retryClearScreenshotsForSession(id).catch(() => {});
170+
const bbId = session.metadata?.bbSessionId;
171+
if (bbId) {
172+
await retryClearScreenshotsForSession(bbId).catch(() => {});
173+
}
161174
} catch (error) {
162175
process.stderr.write(
163176
`[StagehandStore] Error closing session ${id}: ${

src/tools/screenshot.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from "zod";
22
import type { Tool, ToolSchema, ToolResult } from "./tool.js";
33
import type { Context } from "../context.js";
44
import type { ToolActionResult } from "../types/types.js";
5-
import { screenshots } from "../mcp/resources.js";
5+
import { registerScreenshot } from "../mcp/resources.js";
66

77
const ScreenshotInputSchema = z.object({
88
name: z.string().optional().describe("The name of the screenshot"),
@@ -40,7 +40,9 @@ async function handleScreenshot(
4040
.replace(/:/g, "-")}`
4141
: `screenshot-${new Date().toISOString().replace(/:/g, "-")}` +
4242
context.config.browserbaseProjectId;
43-
screenshots.set(name, screenshotBase64);
43+
// Associate with current session id and store in memory
44+
const sessionId = context.currentSessionId;
45+
registerScreenshot(sessionId, name, screenshotBase64);
4446

4547
// Notify the client that the resources changed
4648
const serverInstance = context.getServer();

0 commit comments

Comments
 (0)