@minded-ai/mindedjs 3.1.21 → 3.1.22

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.
Files changed (37) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +0 -4
  3. package/dist/agent.js.map +1 -1
  4. package/dist/browserTask/executeBrowserTask.d.ts.map +1 -1
  5. package/dist/browserTask/executeBrowserTask.js +1 -69
  6. package/dist/browserTask/executeBrowserTask.js.map +1 -1
  7. package/dist/nodes/addPromptNode.d.ts.map +1 -1
  8. package/dist/nodes/addPromptNode.js +1 -0
  9. package/dist/nodes/addPromptNode.js.map +1 -1
  10. package/dist/nodes/addToolRunNode.d.ts.map +1 -1
  11. package/dist/nodes/addToolRunNode.js +1 -0
  12. package/dist/nodes/addToolRunNode.js.map +1 -1
  13. package/dist/platform/toolExecutor.d.ts.map +1 -1
  14. package/dist/platform/toolExecutor.js +1 -0
  15. package/dist/platform/toolExecutor.js.map +1 -1
  16. package/dist/toolsLibrary/browserSessionPersistence.d.ts +28 -0
  17. package/dist/toolsLibrary/browserSessionPersistence.d.ts.map +1 -0
  18. package/dist/toolsLibrary/browserSessionPersistence.js +159 -0
  19. package/dist/toolsLibrary/browserSessionPersistence.js.map +1 -0
  20. package/dist/toolsLibrary/withBrowserSession.d.ts +2 -1
  21. package/dist/toolsLibrary/withBrowserSession.d.ts.map +1 -1
  22. package/dist/toolsLibrary/withBrowserSession.js +65 -50
  23. package/dist/toolsLibrary/withBrowserSession.js.map +1 -1
  24. package/dist/types/Tools.types.d.ts +1 -0
  25. package/dist/types/Tools.types.d.ts.map +1 -1
  26. package/dist/types/Tools.types.js.map +1 -1
  27. package/docs/low-code-editor/rpa-tools.md +4 -1
  28. package/docs/low-code-editor/tools.md +86 -1
  29. package/package.json +2 -2
  30. package/src/agent.ts +0 -5
  31. package/src/browserTask/executeBrowserTask.ts +1 -73
  32. package/src/nodes/addPromptNode.ts +1 -0
  33. package/src/nodes/addToolRunNode.ts +1 -0
  34. package/src/platform/toolExecutor.ts +1 -0
  35. package/src/toolsLibrary/browserSessionPersistence.ts +203 -0
  36. package/src/toolsLibrary/withBrowserSession.ts +74 -56
  37. package/src/types/Tools.types.ts +1 -0
@@ -17,7 +17,6 @@ import { BrowserTaskMode, InvokeBrowserTaskOptions } from './types';
17
17
  import { isLocalOperatorSetup, validateLocalOperatorSetup } from '../cli/localOperatorSetup';
18
18
  import { getConfig } from '../platform/config';
19
19
  import CDP from 'chrome-remote-interface';
20
- import { CookieStore } from './CookieStore';
21
20
  import { ProxyType } from '../types/Tools.types';
22
21
 
23
22
  // Map to track sessionId to instanceId for local browser sessions
@@ -26,9 +25,6 @@ const localSessionInstances = new Map<string, string>();
26
25
  // Map to track CDP clients for cookie management
27
26
  const cdpClients = new Map<string, CDP.Client>();
28
27
 
29
- // Initialize cookie store
30
- const cookieStore = new CookieStore(path.join(process.cwd(), 'local_storage', 'cookies'));
31
-
32
28
  // Helper function to extract port from CDP URL
33
29
  const extractPortFromCdpUrl = (cdpUrl: string): number | null => {
34
30
  try {
@@ -39,61 +35,6 @@ const extractPortFromCdpUrl = (cdpUrl: string): number | null => {
39
35
  }
40
36
  };
41
37
 
42
- // Helper function to load cookies for a session
43
- const loadCookiesForSession = async (sessionId: string): Promise<void> => {
44
- // Check if cookies should be disabled
45
- const noCookies = process.env.MINDED_NO_COOKIES === 'true';
46
- if (noCookies) {
47
- logger.info({ message: '[Browser] Cookies disabled via cookies=false flag', sessionId });
48
- return;
49
- }
50
-
51
- const stored = await cookieStore.load();
52
- if (!stored || stored.length === 0) {
53
- logger.info({ message: '[Browser] No stored cookies to load', sessionId });
54
- return;
55
- }
56
- const cdp = cdpClients.get(sessionId);
57
- if (!cdp) {
58
- logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
59
- return;
60
- }
61
-
62
- try {
63
- await cdp.Network.setCookies({ cookies: stored });
64
- logger.info({ message: '[Browser] Loaded cookies into browser session', sessionId, count: stored.length });
65
- } catch (error) {
66
- logger.warn({ message: '[Browser] Failed to set cookies via CDP', sessionId, error });
67
- }
68
- };
69
-
70
- // Helper function to save cookies for a session
71
- const saveCookiesForSession = async (sessionId: string): Promise<void> => {
72
- // Check if cookies should be disabled
73
- const noCookies = process.env.MINDED_NO_COOKIES === 'true';
74
- if (noCookies) {
75
- return;
76
- }
77
-
78
- try {
79
- const cdp = cdpClients.get(sessionId);
80
- if (!cdp) {
81
- logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
82
- return;
83
- }
84
-
85
- const result = await cdp.Network.getAllCookies();
86
- if (result) {
87
- await cookieStore.save(result.cookies);
88
- logger.info({ message: '[Browser] Saved cookies from browser session', sessionId, count: result.cookies.length });
89
- } else {
90
- logger.warn({ message: '[Browser] No cookies found in browser session', sessionId });
91
- }
92
- } catch (error) {
93
- logger.warn({ message: '[Browser] Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
94
- }
95
- };
96
-
97
38
  // Helper function to cleanup CDP client
98
39
  const cleanupCdpClient = async (sessionId: string): Promise<void> => {
99
40
  const cdp = cdpClients.get(sessionId);
@@ -144,13 +85,6 @@ export const createBrowserSession = async ({
144
85
  logger.info({ message: '[Browser] Created CDP client for session', sessionId, port });
145
86
  }
146
87
 
147
- // Load cookies for this session (best-effort)
148
- try {
149
- await loadCookiesForSession(sessionId);
150
- } catch (error) {
151
- logger.warn({ message: '[Browser] Failed to load cookies for session', sessionId, error });
152
- }
153
-
154
88
  return {
155
89
  sessionId,
156
90
  cdpUrl,
@@ -213,6 +147,7 @@ export const destroyBrowserSession = async ({
213
147
  };
214
148
  }
215
149
 
150
+ // Destroy browser sessions via socket for both cloud and on-prem
216
151
  const response = await mindedConnection.awaitEmit<DestroyBrowserSessionRequest, DestroyBrowserSessionResponse>(
217
152
  mindedConnectionSocketMessageType.DESTROY_BROWSER_SESSION,
218
153
  {
@@ -345,13 +280,6 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
345
280
  }
346
281
  }
347
282
 
348
- // Save cookies after task completes (best-effort)
349
- try {
350
- await saveCookiesForSession(sessionId);
351
- } catch (error) {
352
- logger.warn({ message: '[Browser] Failed to save cookies after task completion', sessionId, error });
353
- }
354
-
355
283
  return {
356
284
  result,
357
285
  steps: [],
@@ -84,6 +84,7 @@ export const addPromptNode = async ({ graph, node, llm, tools, emit, agent }: Ad
84
84
  browserTaskMode,
85
85
  toolCallId,
86
86
  toolName: tool.name,
87
+ persistSession: tool.persistSession,
87
88
  },
88
89
  async (page) => {
89
90
  // Pass page directly to RPA tool execution
@@ -53,6 +53,7 @@ export const addToolRunNode = async ({ graph, tools, toolNode, attachedToNodeNam
53
53
  proxyUser: matchedTool.proxyUser,
54
54
  proxyPass: matchedTool.proxyPass,
55
55
  toolName: matchedTool.name,
56
+ persistSession: matchedTool.persistSession,
56
57
  },
57
58
  async (page) => {
58
59
  // Pass page directly to RPA tool execution
@@ -99,6 +99,7 @@ export class ToolExecutor {
99
99
  proxyCountryCode: tool.proxyCountryCode,
100
100
  proxyType: tool.proxyType,
101
101
  toolName,
102
+ persistSession: tool.persistSession,
102
103
  },
103
104
  async (page) => {
104
105
  // Pass page directly to RPA tool execution
@@ -0,0 +1,203 @@
1
+ import { BrowserContext, Page } from 'playwright';
2
+ import { logger } from '../utils/logger';
3
+ import { saveRecord, getRecords } from './storage';
4
+
5
+ const BROWSER_SESSION_KEY = 'browser-session';
6
+
7
+ interface Cookie {
8
+ name: string;
9
+ value: string;
10
+ domain?: string;
11
+ path?: string;
12
+ expires?: number;
13
+ httpOnly?: boolean;
14
+ secure?: boolean;
15
+ sameSite?: 'Strict' | 'Lax' | 'None';
16
+ }
17
+
18
+ interface LocalStorageEntry {
19
+ url: string;
20
+ data: Record<string, string>;
21
+ }
22
+
23
+ /**
24
+ * Extract localStorage from the current page
25
+ */
26
+ const extractLocalStorageFromPage = async (page: Page): Promise<Record<string, string>> => {
27
+ return await page.evaluate(() => Object.fromEntries(Object.entries(localStorage)));
28
+ };
29
+
30
+ /**
31
+ * Fetch both cookies and localStorage
32
+ */
33
+ export const fetchSessionDataViaSocket = async (): Promise<{
34
+ cookies: Cookie[] | null;
35
+ localStorage: LocalStorageEntry[] | null;
36
+ }> => {
37
+ try {
38
+ const records = await getRecords({
39
+ filters: { key: BROWSER_SESSION_KEY },
40
+ });
41
+
42
+ if (records.length === 0) {
43
+ return { cookies: null, localStorage: null };
44
+ }
45
+
46
+ // Take the last (most recent) record
47
+ const latestRecord = records[records.length - 1];
48
+ const cookies = latestRecord.data?.cookies || null;
49
+ const localStorage = latestRecord.data?.localStorage || null;
50
+
51
+ return { cookies, localStorage };
52
+ } catch (error) {
53
+ logger.warn({ message: 'Failed to fetch session data', error });
54
+ return { cookies: null, localStorage: null };
55
+ }
56
+ };
57
+
58
+ /**
59
+ * Save both cookies and localStorage for ALL origins
60
+ */
61
+ export const saveSessionDataViaSocket = async (
62
+ context: BrowserContext,
63
+ page: Page,
64
+ sessionId: string,
65
+ ): Promise<void> => {
66
+ try {
67
+ const cookies = await context.cookies();
68
+ const data: Record<string, unknown> = {};
69
+
70
+ if (cookies && cookies.length > 0) {
71
+ data.cookies = cookies;
72
+ }
73
+
74
+ // Extract localStorage for ALL origins using CDP
75
+ let client;
76
+ try {
77
+ client = await context.newCDPSession(page);
78
+
79
+ // Enable Target domain to track all frames and their origins
80
+ await client.send('Target.setDiscoverTargets', { discover: true });
81
+
82
+ // Get all targets (frames, workers, etc.)
83
+ type TargetInfo = {
84
+ targetId: string;
85
+ type: string;
86
+ title: string;
87
+ url: string;
88
+ attached: boolean;
89
+ };
90
+
91
+ const response = await client.send('Target.getTargets');
92
+ const targetInfos = (response as { targetInfos: TargetInfo[] }).targetInfos;
93
+
94
+ // Extract unique origins from all page-type targets
95
+ const origins = new Set<string>();
96
+ for (const target of targetInfos || []) {
97
+ if (target.type === 'page' || target.type === 'iframe') {
98
+ try {
99
+ const url = new URL(target.url);
100
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
101
+ origins.add(url.origin);
102
+ }
103
+ } catch {
104
+ // Ignore invalid URLs
105
+ }
106
+ }
107
+ }
108
+
109
+ // Also add the current page origin
110
+ try {
111
+ const currentOrigin = new URL(page.url()).origin;
112
+ origins.add(currentOrigin);
113
+ } catch {
114
+ // Ignore if current URL is invalid
115
+ }
116
+
117
+ // Extract localStorage from all origins in parallel
118
+ const extractionPromises = Array.from(origins).map(async (origin) => {
119
+ try {
120
+ type DOMStorageResponse = {
121
+ entries: Array<[string, string]>;
122
+ };
123
+
124
+ const storageResponse = await client!.send('DOMStorage.getDOMStorageItems', {
125
+ storageId: {
126
+ securityOrigin: origin,
127
+ isLocalStorage: true,
128
+ },
129
+ });
130
+ const { entries } = storageResponse as DOMStorageResponse;
131
+
132
+ if (entries && entries.length > 0) {
133
+ const data = Object.fromEntries(entries);
134
+
135
+ // Check payload size to prevent WebSocket errors (10MB limit as safety margin)
136
+ const dataSize = JSON.stringify(data).length;
137
+ if (dataSize > 10 * 1024 * 1024) {
138
+ logger.warn({
139
+ message: 'localStorage data too large, skipping origin',
140
+ origin,
141
+ sizeMB: (dataSize / 1024 / 1024).toFixed(2)
142
+ });
143
+ return null;
144
+ }
145
+
146
+ return {
147
+ url: origin,
148
+ data,
149
+ };
150
+ }
151
+ } catch (err) {
152
+ logger.warn({ message: 'Failed to extract localStorage for origin', origin, error: err });
153
+ }
154
+ return null;
155
+ });
156
+
157
+ const results = await Promise.all(extractionPromises);
158
+ const localStorageByOrigin = results.filter(Boolean) as LocalStorageEntry[];
159
+
160
+ if (localStorageByOrigin.length > 0) {
161
+ data.localStorage = localStorageByOrigin;
162
+ }
163
+ } catch (err) {
164
+ // Fallback: If CDP approach fails, use current page only
165
+ logger.warn({ message: 'Failed to extract localStorage via CDP, falling back to current page', error: err });
166
+ try {
167
+ const currentUrl = page.url();
168
+ const localStorageData = await extractLocalStorageFromPage(page);
169
+
170
+ if (Object.keys(localStorageData).length > 0) {
171
+ data.localStorage = [{
172
+ url: currentUrl,
173
+ data: localStorageData
174
+ }];
175
+ }
176
+ } catch (fallbackErr) {
177
+ logger.warn({ message: 'Failed to extract localStorage via fallback method', error: fallbackErr });
178
+ }
179
+ } finally {
180
+ if (client) {
181
+ await client.detach().catch(() => {});
182
+ }
183
+ }
184
+
185
+ if (Object.keys(data).length === 0) {
186
+ return;
187
+ }
188
+
189
+ // Add key to data for filtering
190
+ data.key = BROWSER_SESSION_KEY;
191
+
192
+ // Save with the actual sessionId, not a constant key
193
+ // TTL: 10 days (in seconds)
194
+ const TTL_10_DAYS = 10 * 24 * 60 * 60;
195
+ try {
196
+ await saveRecord(sessionId, data, TTL_10_DAYS);
197
+ } catch (error) {
198
+ logger.warn({ message: 'Failed to save session data', sessionId, error });
199
+ }
200
+ } catch (error) {
201
+ logger.warn({ message: 'Failed to save session data', sessionId, error });
202
+ }
203
+ };
@@ -4,13 +4,10 @@ import { createBrowserSession, destroyBrowserSession } from '../browserTask/exec
4
4
  import { logger } from '../utils/logger';
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
- import { CookieStore } from '../browserTask/CookieStore';
8
7
  import { awaitEmit, emit } from '../platform/mindedConnection';
9
8
  import { mindedConnectionSocketMessageType } from '../platform/mindedConnectionTypes';
10
9
  import { ProxyType } from '../types/Tools.types';
11
-
12
- // Initialize cookie store
13
- const cookieStore = new CookieStore(path.join(process.cwd(), 'local_storage', 'cookies'));
10
+ import { fetchSessionDataViaSocket, saveSessionDataViaSocket } from './browserSessionPersistence';
14
11
 
15
12
  /**
16
13
  * Strips unnecessary content from HTML to reduce file size
@@ -85,7 +82,6 @@ class ScreenshotCapture {
85
82
  error,
86
83
  });
87
84
  });
88
-
89
85
  } catch (error) {
90
86
  logger.warn({
91
87
  message: 'Failed to capture screenshot',
@@ -141,7 +137,7 @@ class LogsCapture {
141
137
  try {
142
138
  const timestamp = new Date().toISOString();
143
139
 
144
- // Send logs to backend via socket (upload all logs accumulated so far)
140
+ // Send full log history to backend - backend overwrites the file on each upload
145
141
  await awaitEmit(mindedConnectionSocketMessageType.RPA_LOGS_UPLOAD, {
146
142
  type: mindedConnectionSocketMessageType.RPA_LOGS_UPLOAD,
147
143
  sessionId: this.sessionId,
@@ -216,13 +212,20 @@ const createProxyLocator = (
216
212
  /**
217
213
  * Create a proxy page that intercepts actions and captures screenshots and logs
218
214
  */
219
- const createProxyPage = (
220
- page: Page,
221
- screenshotCapture: ScreenshotCapture,
222
- logsCapture: LogsCapture,
223
- ): Page => {
215
+ const createProxyPage = (page: Page, screenshotCapture: ScreenshotCapture, logsCapture: LogsCapture): Page => {
224
216
  // List of methods to intercept for screenshot capture and logging
225
- const methodsToIntercept = ['click', 'fill', 'type', 'goto', 'selectOption', 'check', 'uncheck', 'setInputFiles', 'waitForTimeout', 'evaluate'];
217
+ const methodsToIntercept = [
218
+ 'click',
219
+ 'fill',
220
+ 'type',
221
+ 'goto',
222
+ 'selectOption',
223
+ 'check',
224
+ 'uncheck',
225
+ 'setInputFiles',
226
+ 'waitForTimeout',
227
+ 'evaluate',
228
+ ];
226
229
  // Methods that return locators and need special handling
227
230
  const locatorMethods = ['locator'];
228
231
 
@@ -323,37 +326,6 @@ const formatLocatorActionLog = (action: string, selector: string | undefined, ar
323
326
  }
324
327
  };
325
328
 
326
- // Helper function to load cookies for a session
327
- const loadCookiesForSession = async (context: BrowserContext, sessionId: string): Promise<void> => {
328
- const stored = await cookieStore.load();
329
- if (!stored || stored.length === 0) {
330
- logger.info({ message: 'No stored cookies to load', sessionId });
331
- return;
332
- }
333
-
334
- try {
335
- await context.addCookies(stored);
336
- logger.info({ message: 'Loaded cookies into browser session', sessionId, count: stored.length });
337
- } catch (error) {
338
- logger.warn({ message: 'Failed to add cookies to context', sessionId, error });
339
- }
340
- };
341
-
342
- // Helper function to save cookies for a session
343
- const saveCookiesForSession = async (context: BrowserContext, sessionId: string): Promise<void> => {
344
- try {
345
- const cookies = await context.cookies();
346
- if (cookies && cookies.length > 0) {
347
- await cookieStore.save(cookies);
348
- logger.info({ message: 'Saved cookies from browser session', sessionId, count: cookies.length });
349
- } else {
350
- logger.info({ message: 'No cookies found in browser session', sessionId });
351
- }
352
- } catch (error) {
353
- logger.warn({ message: 'Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
354
- }
355
- };
356
-
357
329
  export const withBrowserSession = async <T>(
358
330
  {
359
331
  sessionId,
@@ -365,6 +337,7 @@ export const withBrowserSession = async <T>(
365
337
  proxyUser,
366
338
  proxyPass,
367
339
  toolName,
340
+ persistSession = true,
368
341
  }: {
369
342
  sessionId: string;
370
343
  browserTaskMode: BrowserTaskMode;
@@ -375,10 +348,19 @@ export const withBrowserSession = async <T>(
375
348
  proxyUser?: string;
376
349
  proxyPass?: string;
377
350
  toolName: string;
351
+ persistSession?: boolean;
378
352
  },
379
353
  callback: (page: Page) => Promise<T>,
380
354
  ): Promise<{ result: T }> => {
381
- const { cdpUrl } = await createBrowserSession({ sessionId, browserTaskMode, proxyCountryCode, proxyType, proxyServer, proxyUser, proxyPass });
355
+ const { cdpUrl } = await createBrowserSession({
356
+ sessionId,
357
+ browserTaskMode,
358
+ proxyCountryCode,
359
+ proxyType,
360
+ proxyServer,
361
+ proxyUser,
362
+ proxyPass,
363
+ });
382
364
  if (!cdpUrl) {
383
365
  throw new Error('CDP URL was not provided by the browser session.');
384
366
  }
@@ -409,16 +391,52 @@ export const withBrowserSession = async <T>(
409
391
  const logsCapture = new LogsCapture(sessionId, toolCallId);
410
392
  page = createProxyPage(page, screenshotCapture, logsCapture);
411
393
 
412
- // Load cookies for this session (only for local mode, best-effort)
413
- const noCookies = process.env.MINDED_NO_COOKIES === 'true';
414
- if (browserTaskMode === BrowserTaskMode.LOCAL && !noCookies) {
394
+ // Load session data (cookies + localStorage) once at startup (if persistence is enabled)
395
+ if (persistSession) {
415
396
  try {
416
- await loadCookiesForSession(context, sessionId);
397
+ const { cookies, localStorage: localStorageEntries } = await fetchSessionDataViaSocket();
398
+
399
+ // Load cookies into browser context
400
+ if (cookies && cookies.length > 0) {
401
+ await context.addCookies(cookies);
402
+ logger.info({ message: 'Loaded cookies into browser session', sessionId, count: cookies.length });
403
+ }
404
+
405
+ // Inject localStorage for ALL origins using addInitScript
406
+ // This runs on EVERY page load, but only sets items that don't already exist
407
+ // This prevents overwriting user changes during the session while restoring stored data
408
+ if (localStorageEntries && localStorageEntries.length > 0) {
409
+ const localStorageByOrigin = localStorageEntries.reduce(
410
+ (acc, entry) => {
411
+ try {
412
+ const origin = new URL(entry.url).origin;
413
+ acc[origin] = entry.data;
414
+ } catch (error) {
415
+ logger.warn({ message: 'Failed to parse origin for localStorage', url: entry.url, error });
416
+ }
417
+ return acc;
418
+ },
419
+ {} as Record<string, Record<string, string>>,
420
+ );
421
+
422
+ await context.addInitScript((storageData: Record<string, Record<string, string>>) => {
423
+ const currentOrigin = window.location.origin;
424
+ const dataForOrigin = storageData[currentOrigin];
425
+
426
+ if (dataForOrigin) {
427
+ // Only set items that don't already exist (non-destructive merge)
428
+ // This preserves any changes made by the user during the current session
429
+ for (const [key, value] of Object.entries(dataForOrigin)) {
430
+ if (localStorage.getItem(key) === null) {
431
+ localStorage.setItem(key, value);
432
+ }
433
+ }
434
+ }
435
+ }, localStorageByOrigin);
436
+ }
417
437
  } catch (error) {
418
- logger.warn({ message: 'Failed to load cookies for session', sessionId, error });
438
+ logger.warn({ message: 'Failed to load session data', sessionId, error });
419
439
  }
420
- } else if (noCookies) {
421
- logger.info({ message: 'Cookies disabled via cookies=false flag', sessionId });
422
440
  }
423
441
 
424
442
  // Configure download behavior via CDP
@@ -532,12 +550,12 @@ export const withBrowserSession = async <T>(
532
550
  }
533
551
  }
534
552
 
535
- // Save cookies before destroying session (only for local mode, best-effort)
536
- if (browserTaskMode === BrowserTaskMode.LOCAL && !noCookies) {
553
+ // Save cookies and localStorage before destroying session (if persistence is enabled)
554
+ if (persistSession) {
537
555
  try {
538
- await saveCookiesForSession(context, sessionId);
556
+ await saveSessionDataViaSocket(context, page, sessionId);
539
557
  } catch (error) {
540
- logger.warn({ message: 'Failed to save cookies before session destruction', sessionId, error });
558
+ logger.warn({ message: 'Failed to save session data', sessionId, error });
541
559
  }
542
560
  }
543
561
 
@@ -545,7 +563,7 @@ export const withBrowserSession = async <T>(
545
563
  sessionId,
546
564
  localRun: browserTaskMode === BrowserTaskMode.LOCAL,
547
565
  onPrem: browserTaskMode === BrowserTaskMode.ON_PREM,
548
- }).catch(() => { });
566
+ }).catch(() => {});
549
567
  }
550
568
  };
551
569
 
@@ -33,6 +33,7 @@ export interface RPATool<Input extends z.ZodSchema, Memory = any, Output extends
33
33
  proxyUser?: string; // Custom proxy username - used when proxyType is CUSTOM
34
34
  proxyPass?: string; // Custom proxy password - used when proxyType is CUSTOM
35
35
  browserTaskMode?: BrowserTaskMode; // Optional browser provider (local/cloud/onPrem)
36
+ persistSession?: boolean; // Session persistence: true (global persistence with cloud storage, default), false (no persistence)
36
37
  execute: (input: RPAToolExecuteInput<Input, Memory>) => Promise<ToolReturnType>;
37
38
  }
38
39