@minded-ai/mindedjs 3.1.44 → 3.1.46

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.
@@ -6,6 +6,13 @@ import { HistoryStep, TriggerHistoryStep } from '../types/Agent.types';
6
6
  import { State } from '../types/LangGraph.types';
7
7
  import { NodeType } from '../types/Flows.types';
8
8
 
9
+ // Matches dotted path identifiers — allows letters, digits, `_`, `$`, `.`, `[`, `]`, spaces, and hyphens
10
+ // (spaces/hyphens support node display names like "Greeting Prompt" and trigger keys like "some-key").
11
+ // Implicitly rejects JSON braces (which contain `"` and `:`) since those chars are not in the set.
12
+ const PLACEHOLDER_PATH_CHARS = String.raw`[A-Za-z0-9_.$\[\] -]`;
13
+ const PLACEHOLDER_REGEX = new RegExp(String.raw`\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}`, 'g');
14
+ const SINGLE_PLACEHOLDER_REGEX = new RegExp(String.raw`^\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}$`);
15
+
9
16
  /**
10
17
  * Extract node outputs from state.history and state.messages
11
18
  * Returns a map of nodeId/nodeDisplayName -> output object
@@ -129,29 +136,72 @@ export type ExtendedContextData = ContextData & {
129
136
  */
130
137
  export function compileParameter(parameter: string, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
131
138
  try {
132
- context = extendContextData(context);
139
+ return compileParameterWithContext(parameter, extendContextData(context), inputKey, inputSchema);
140
+ } catch (err) {
141
+ logger.error({ message: 'Error compiling parameter', err, parameter });
142
+ return parameter;
143
+ }
144
+ }
133
145
 
134
- // First, render with EJS
135
- let compiledParameter = ejs.render(parameter, context).trim();
146
+ /**
147
+ * Recursively compile placeholders inside nested objects/arrays.
148
+ * Top-level strings get full schema-based coercion; nested string leaves are compiled without schema coercion.
149
+ */
150
+ export function compileValueDeep(value: unknown, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
151
+ if (typeof value === 'string') return compileParameter(value, context, inputKey, inputSchema);
152
+ if (!value || typeof value !== 'object') return value;
153
+ const extCtx = extendContextData(context);
154
+ return compileValueDeepInternal(value, extCtx);
155
+ }
136
156
 
137
- // Then, replace placeholders in {} format
138
- // If the result contains only one set of curly braces, get the native value at that key path, e.g., {state.memory.arrayOfStrings} -> state.memory.arrayOfStrings
139
- // Otherwise, compile it into a string.
140
- const keyPathMatch = compiledParameter.match(/^\{([^}]+)}$/);
141
- if (keyPathMatch) {
142
- const keyPath = keyPathMatch[1];
143
- compiledParameter = getContextValueFromPath(keyPath, context, compiledParameter);
144
- } else {
145
- compiledParameter = replacePlaceholders(compiledParameter, context);
157
+ function compileValueDeepInternal(value: unknown, context: ExtendedContextData): any {
158
+ if (typeof value === 'string') {
159
+ try {
160
+ return compileParameterWithContext(value, context);
161
+ } catch (err) {
162
+ logger.error({ message: 'Error compiling nested parameter', err, value });
163
+ return value;
146
164
  }
165
+ }
166
+ if (!value || typeof value !== 'object') return value;
167
+ if (Array.isArray(value)) return value.map((v) => compileValueDeepInternal(v, context));
168
+ return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, compileValueDeepInternal(v, context)]));
169
+ }
147
170
 
148
- compiledParameter = coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
171
+ /** Shared implementation expects already-extended context. */
172
+ function compileParameterWithContext(parameter: string, context: ExtendedContextData, inputKey?: string, inputSchema?: ZodSchema): any {
173
+ let compiledParameter = ejs.render(parameter, context).trim();
149
174
 
150
- return compiledParameter;
151
- } catch (err) {
152
- logger.error({ message: 'Error compiling parameter', err, parameter });
153
- return parameter; // Return uncompiled if there's an error
175
+ // Single placeholder → return native value; otherwise replace all placeholders as strings.
176
+ // Regex matches path identifiers (allows spaces/hyphens for node display names), rejects JSON braces.
177
+ const keyPathMatch = compiledParameter.match(SINGLE_PLACEHOLDER_REGEX);
178
+ if (keyPathMatch) {
179
+ compiledParameter = getContextValueFromPath(keyPathMatch[1].trim(), context, compiledParameter);
180
+ } else {
181
+ compiledParameter = replacePlaceholders(compiledParameter, context);
154
182
  }
183
+
184
+ return coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
185
+ }
186
+
187
+ /**
188
+ * Compile node parameters with placeholder replacement and type coercion.
189
+ * Use getSchemaForToolInference() separately to filter the schema for the LLM.
190
+ */
191
+ export function compileNodeParameters(
192
+ parameters: Record<string, any> | undefined,
193
+ context: ContextData,
194
+ schema?: ZodSchema,
195
+ ): Record<string, any> {
196
+ const compiledParameters: Record<string, any> = {};
197
+
198
+ for (const [key, value] of Object.entries(parameters || {})) {
199
+ if (value !== '') {
200
+ compiledParameters[key] = compileValueDeep(value, context, key, schema);
201
+ }
202
+ }
203
+
204
+ return compiledParameters;
155
205
  }
156
206
 
157
207
  /**
@@ -161,12 +211,8 @@ export function compilePrompt(prompt: string, context: ContextData): string {
161
211
  try {
162
212
  context = extendContextData(context);
163
213
 
164
- // First, render with EJS
165
214
  let compiledPrompt = ejs.render(prompt, context);
166
-
167
- // Then, replace placeholders in {} format
168
215
  compiledPrompt = replacePlaceholders(compiledPrompt, context);
169
-
170
216
  return compiledPrompt;
171
217
  } catch (err) {
172
218
  logger.error({ message: 'Error compiling prompt', err, prompt });
@@ -215,10 +261,10 @@ function extendContextData(context: ContextData = {}): ExtendedContextData {
215
261
  * - {tools.NodeName.field} - node reference with 'tools' prefix (alias)
216
262
  */
217
263
  function replacePlaceholders(text: string, context: Record<string, any>): string {
218
- return text.replace(/\{([^}]+)}/g, (match, keyPath) => {
264
+ return text.replace(PLACEHOLDER_REGEX, (match, keyPathRaw) => {
265
+ const keyPath = String(keyPathRaw).trim();
219
266
  const value = getContextValueFromPath(keyPath, context, match);
220
267
 
221
- // In the case of complex values (e.g., arrays or objects), JSON stringify them to ensure all data is preserved.
222
268
  if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
223
269
  try {
224
270
  return JSON.stringify(value);
@@ -3,6 +3,7 @@ import { logger } from '../utils/logger';
3
3
  import { saveRecord, getRecords } from './storage';
4
4
 
5
5
  const BROWSER_SESSION_KEY = 'browser-session';
6
+ const STORAGE_OPERATION_TIMEOUT_MS = 3000;
6
7
 
7
8
  interface Cookie {
8
9
  name: string;
@@ -20,11 +21,34 @@ interface LocalStorageEntry {
20
21
  data: Record<string, string>;
21
22
  }
22
23
 
24
+ const withTimeout = async <T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T> => {
25
+ let timeout: NodeJS.Timeout | null = null;
26
+
27
+ try {
28
+ return await Promise.race([
29
+ promise,
30
+ new Promise<never>((_, reject) => {
31
+ timeout = setTimeout(() => {
32
+ reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
33
+ }, timeoutMs);
34
+ }),
35
+ ]);
36
+ } finally {
37
+ if (timeout) {
38
+ clearTimeout(timeout);
39
+ }
40
+ }
41
+ };
42
+
23
43
  /**
24
44
  * Extract localStorage from the current page
25
45
  */
26
46
  const extractLocalStorageFromPage = async (page: Page): Promise<Record<string, string>> => {
27
- return await page.evaluate(() => Object.fromEntries(Object.entries(localStorage)));
47
+ return await withTimeout(
48
+ page.evaluate(() => Object.fromEntries(Object.entries(localStorage))),
49
+ STORAGE_OPERATION_TIMEOUT_MS,
50
+ 'Extract localStorage from current page',
51
+ );
28
52
  };
29
53
 
30
54
  /**
@@ -64,7 +88,11 @@ export const saveSessionDataViaSocket = async (
64
88
  sessionId: string,
65
89
  ): Promise<void> => {
66
90
  try {
67
- const cookies = await context.cookies();
91
+ const cookies = await withTimeout(
92
+ context.cookies(),
93
+ STORAGE_OPERATION_TIMEOUT_MS,
94
+ 'Extract browser cookies',
95
+ );
68
96
  const data: Record<string, unknown> = {};
69
97
 
70
98
  if (cookies && cookies.length > 0) {
@@ -74,10 +102,18 @@ export const saveSessionDataViaSocket = async (
74
102
  // Extract localStorage for ALL origins using CDP
75
103
  let client;
76
104
  try {
77
- client = await context.newCDPSession(page);
105
+ client = await withTimeout(
106
+ context.newCDPSession(page),
107
+ STORAGE_OPERATION_TIMEOUT_MS,
108
+ 'Create CDP session for browser session persistence',
109
+ );
78
110
 
79
111
  // Enable Target domain to track all frames and their origins
80
- await client.send('Target.setDiscoverTargets', { discover: true });
112
+ await withTimeout(
113
+ client.send('Target.setDiscoverTargets', { discover: true }),
114
+ STORAGE_OPERATION_TIMEOUT_MS,
115
+ 'Enable CDP target discovery',
116
+ );
81
117
 
82
118
  // Get all targets (frames, workers, etc.)
83
119
  type TargetInfo = {
@@ -88,7 +124,11 @@ export const saveSessionDataViaSocket = async (
88
124
  attached: boolean;
89
125
  };
90
126
 
91
- const response = await client.send('Target.getTargets');
127
+ const response = await withTimeout(
128
+ client.send('Target.getTargets'),
129
+ STORAGE_OPERATION_TIMEOUT_MS,
130
+ 'Get CDP targets for localStorage persistence',
131
+ );
92
132
  const targetInfos = (response as { targetInfos: TargetInfo[] }).targetInfos;
93
133
 
94
134
  // Extract unique origins from all page-type targets
@@ -121,12 +161,16 @@ export const saveSessionDataViaSocket = async (
121
161
  entries: Array<[string, string]>;
122
162
  };
123
163
 
124
- const storageResponse = await client!.send('DOMStorage.getDOMStorageItems', {
125
- storageId: {
126
- securityOrigin: origin,
127
- isLocalStorage: true,
128
- },
129
- });
164
+ const storageResponse = await withTimeout(
165
+ client!.send('DOMStorage.getDOMStorageItems', {
166
+ storageId: {
167
+ securityOrigin: origin,
168
+ isLocalStorage: true,
169
+ },
170
+ }),
171
+ STORAGE_OPERATION_TIMEOUT_MS,
172
+ `Extract localStorage for origin ${origin}`,
173
+ );
130
174
  const { entries } = storageResponse as DOMStorageResponse;
131
175
 
132
176
  if (entries && entries.length > 0) {
@@ -178,7 +222,11 @@ export const saveSessionDataViaSocket = async (
178
222
  }
179
223
  } finally {
180
224
  if (client) {
181
- await client.detach().catch(() => { });
225
+ await withTimeout(
226
+ client.detach(),
227
+ STORAGE_OPERATION_TIMEOUT_MS,
228
+ 'Detach CDP session after browser session persistence',
229
+ ).catch(() => { });
182
230
  }
183
231
  }
184
232
 
@@ -158,6 +158,7 @@ export interface BrowserTaskNode extends BaseNode {
158
158
  model?: string;
159
159
  inputSchema?: SchemaField[];
160
160
  outputSchema?: OutputSchemaField[];
161
+ parameters?: Record<string, any>;
161
162
  autoTrigger?: boolean;
162
163
  defaultPayload?: string;
163
164
  proxyConfig?: ProxyConfig; // Proxy configuration supporting all proxy types