@minded-ai/mindedjs 3.1.21 → 3.1.23

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 (45) 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/cli/validateAgent.d.ts.map +1 -1
  8. package/dist/cli/validateAgent.js +15 -0
  9. package/dist/cli/validateAgent.js.map +1 -1
  10. package/dist/nodes/addPromptNode.d.ts.map +1 -1
  11. package/dist/nodes/addPromptNode.js +1 -0
  12. package/dist/nodes/addPromptNode.js.map +1 -1
  13. package/dist/nodes/addToolRunNode.d.ts.map +1 -1
  14. package/dist/nodes/addToolRunNode.js +1 -0
  15. package/dist/nodes/addToolRunNode.js.map +1 -1
  16. package/dist/platform/toolExecutor.d.ts.map +1 -1
  17. package/dist/platform/toolExecutor.js +1 -0
  18. package/dist/platform/toolExecutor.js.map +1 -1
  19. package/dist/toolsLibrary/browserSessionPersistence.d.ts +28 -0
  20. package/dist/toolsLibrary/browserSessionPersistence.d.ts.map +1 -0
  21. package/dist/toolsLibrary/browserSessionPersistence.js +159 -0
  22. package/dist/toolsLibrary/browserSessionPersistence.js.map +1 -0
  23. package/dist/toolsLibrary/withBrowserSession.d.ts +2 -1
  24. package/dist/toolsLibrary/withBrowserSession.d.ts.map +1 -1
  25. package/dist/toolsLibrary/withBrowserSession.js +65 -50
  26. package/dist/toolsLibrary/withBrowserSession.js.map +1 -1
  27. package/dist/types/Flows.types.d.ts +0 -5
  28. package/dist/types/Flows.types.d.ts.map +1 -1
  29. package/dist/types/Flows.types.js.map +1 -1
  30. package/dist/types/Tools.types.d.ts +1 -0
  31. package/dist/types/Tools.types.d.ts.map +1 -1
  32. package/dist/types/Tools.types.js.map +1 -1
  33. package/docs/low-code-editor/rpa-tools.md +4 -1
  34. package/docs/low-code-editor/tools.md +86 -1
  35. package/package.json +1 -1
  36. package/src/agent.ts +0 -5
  37. package/src/browserTask/executeBrowserTask.ts +1 -73
  38. package/src/cli/validateAgent.ts +17 -0
  39. package/src/nodes/addPromptNode.ts +1 -0
  40. package/src/nodes/addToolRunNode.ts +1 -0
  41. package/src/platform/toolExecutor.ts +1 -0
  42. package/src/toolsLibrary/browserSessionPersistence.ts +203 -0
  43. package/src/toolsLibrary/withBrowserSession.ts +74 -56
  44. package/src/types/Flows.types.ts +0 -5
  45. package/src/types/Tools.types.ts +1 -0
@@ -7,10 +7,16 @@ Tools are reusable functions that agents can call to perform specific actions li
7
7
  Every tool must implement the `Tool` interface:
8
8
 
9
9
  ```ts
10
- interface Tool<Input extends z.ZodSchema, Memory> {
10
+ // Example: Tool with input and optional output schemas
11
+ interface Tool<
12
+ Input extends z.ZodSchema,
13
+ Memory = any,
14
+ Output extends z.ZodSchema = z.ZodTypeAny
15
+ > {
11
16
  name: string; // Unique tool identifier
12
17
  description: string; // What the tool does (used by LLM)
13
18
  input: Input; // Zod schema for input validation
19
+ output?: Output; // Optional: Zod schema for output validation (e.g., outputSchema)
14
20
  isGlobal?: boolean; // Optional: available across all LLM calls
15
21
  execute: ({ input, state, agent }) => Promise<{ result? }>;
16
22
  }
@@ -140,6 +146,85 @@ const updateOrderSchema = z.object({
140
146
  });
141
147
  ```
142
148
 
149
+ ## Output Schema Configuration
150
+
151
+ Tools can optionally define an output schema to validate and structure their return values. This provides type safety and clear documentation of what the tool returns.
152
+
153
+ ### Defining Output Schema
154
+
155
+ **Important**: The output schema variable must be named `outputSchema` for the platform to parse it correctly.
156
+
157
+ ```ts
158
+ import { z } from 'zod';
159
+ import { Tool } from '@minded-ai/mindedjs';
160
+ import memorySchema from '../schema';
161
+
162
+ type Memory = z.infer<typeof memorySchema>;
163
+
164
+ const inputSchema = z.object({
165
+ email: z.string().describe('User email address'),
166
+ password: z.string().describe('User password'),
167
+ });
168
+
169
+ // Must be named 'outputSchema'
170
+ const outputSchema = z.object({
171
+ success: z.boolean().describe('Status of the login operation'),
172
+ message: z.string().describe('Login status message'),
173
+ url: z.string().describe('Dashboard URL after successful login'),
174
+ usedPersistedSession: z.boolean().describe('Whether a persisted session was used'),
175
+ });
176
+
177
+ const loginTool: Tool<typeof inputSchema, Memory, typeof outputSchema> = {
178
+ name: 'login_to_dashboard',
179
+ description: 'Login to dashboard and return access details',
180
+ input: inputSchema,
181
+ output: outputSchema,
182
+ type: 'rpa',
183
+ execute: async ({ input, state, agent, page }): Promise<{ result: z.infer<typeof outputSchema> }> => {
184
+ // Tool logic here
185
+ const loginResult = await performLogin(page, input.email, input.password);
186
+
187
+ return {
188
+ result: {
189
+ success: true,
190
+ message: 'Successfully logged in',
191
+ url: page.url(),
192
+ usedPersistedSession: false,
193
+ },
194
+ };
195
+ },
196
+ };
197
+
198
+ export default loginTool;
199
+ ```
200
+
201
+ ### Benefits of Output Schema
202
+
203
+ - **Type Safety**: TypeScript will enforce that your return value matches the output schema
204
+ - **Validation**: Zod validates the output at runtime to catch bugs early
205
+ - **Documentation**: Output schema serves as clear documentation of what the tool returns
206
+ - **LLM Context**: The LLM receives structured information about the tool's output format
207
+
208
+ ### Output Schema vs Result
209
+
210
+ - **Without output schema**: Return any value in `result`, no validation
211
+ - **With output schema**: Return value must match the schema structure
212
+
213
+ ```ts
214
+ // Without output schema - any result
215
+ return { result: 'Login successful' };
216
+
217
+ // With output schema - typed and validated
218
+ return {
219
+ result: {
220
+ success: true,
221
+ message: 'Login successful',
222
+ url: 'https://dashboard.example.com',
223
+ usedPersistedSession: false,
224
+ },
225
+ };
226
+ ```
227
+
143
228
  ## Tool Registration
144
229
 
145
230
  ### Global Tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minded-ai/mindedjs",
3
- "version": "3.1.21",
3
+ "version": "3.1.23",
4
4
  "description": "MindedJS is a TypeScript library for building agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/agent.ts CHANGED
@@ -1276,11 +1276,6 @@ export class Agent {
1276
1276
 
1277
1277
  const { toolName, params, flags } = parseDirectToolExecutionArgs(toolArgs);
1278
1278
 
1279
- // Set environment variable for cookies flag so browser session can read it
1280
- if (flags.noCookies) {
1281
- process.env.MINDED_NO_COOKIES = 'true';
1282
- }
1283
-
1284
1279
  logger.info({
1285
1280
  message: '[runTool] Executing tool',
1286
1281
  toolName,
@@ -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: [],
@@ -480,6 +480,23 @@ export async function runValidateCommand(agentPath?: string): Promise<void> {
480
480
  process.exit(1);
481
481
  }
482
482
 
483
+ // Check if mainFlow.yaml exists in any of the flows folders
484
+ let mainFlowFound = false;
485
+ for (const folder of flowsFolders) {
486
+ const resolvedFolder = path.isAbsolute(folder) ? folder : path.join(projectDir, folder);
487
+ const mainFlowPath = path.join(resolvedFolder, 'mainFlow.yaml');
488
+ if (fs.existsSync(mainFlowPath)) {
489
+ mainFlowFound = true;
490
+ break;
491
+ }
492
+ }
493
+
494
+ if (!mainFlowFound) {
495
+ console.error('Error: mainFlow.yaml not found in any configured flows folder');
496
+ console.error('The flows directory must contain a mainFlow.yaml file');
497
+ process.exit(1);
498
+ }
499
+
483
500
  console.log(`\nValidating ${flowFiles.length} flow(s)...\n`);
484
501
 
485
502
  const graphCompilationErrors: string[] = [];
@@ -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[0];
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
+ };