@minded-ai/mindedjs 2.0.43 → 2.0.45-beta.1

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 (36) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +29 -1
  3. package/dist/agent.js.map +1 -1
  4. package/dist/browserTask/executeBrowserTask.js +34 -33
  5. package/dist/browserTask/executeBrowserTask.js.map +1 -1
  6. package/dist/nodes/addThinkingNode.d.ts.map +1 -1
  7. package/dist/nodes/addThinkingNode.js +36 -11
  8. package/dist/nodes/addThinkingNode.js.map +1 -1
  9. package/dist/nodes/compilePrompt.d.ts.map +1 -1
  10. package/dist/nodes/compilePrompt.js +84 -4
  11. package/dist/nodes/compilePrompt.js.map +1 -1
  12. package/dist/platform/mindedConnectionTypes.d.ts +12 -1
  13. package/dist/platform/mindedConnectionTypes.d.ts.map +1 -1
  14. package/dist/platform/mindedConnectionTypes.js +1 -0
  15. package/dist/platform/mindedConnectionTypes.js.map +1 -1
  16. package/dist/types/Flows.types.d.ts +36 -23
  17. package/dist/types/Flows.types.d.ts.map +1 -1
  18. package/dist/types/Flows.types.js.map +1 -1
  19. package/dist/types/Tools.types.d.ts +5 -3
  20. package/dist/types/Tools.types.d.ts.map +1 -1
  21. package/dist/types/Tools.types.js.map +1 -1
  22. package/dist/utils/schemaConverter.d.ts +3 -0
  23. package/dist/utils/schemaConverter.d.ts.map +1 -0
  24. package/dist/utils/schemaConverter.js +73 -0
  25. package/dist/utils/schemaConverter.js.map +1 -0
  26. package/docs/low-code-editor/rpa-tools.md +17 -8
  27. package/package.json +2 -2
  28. package/src/agent.ts +40 -8
  29. package/src/browserTask/executeBrowserTask.ts +34 -34
  30. package/src/nodes/addPromptNode.ts +1 -1
  31. package/src/nodes/addThinkingNode.ts +40 -12
  32. package/src/nodes/compilePrompt.ts +92 -4
  33. package/src/platform/mindedConnectionTypes.ts +13 -0
  34. package/src/types/Flows.types.ts +39 -32
  35. package/src/types/Tools.types.ts +7 -3
  36. package/src/utils/schemaConverter.ts +82 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minded-ai/mindedjs",
3
- "version": "2.0.43",
3
+ "version": "2.0.45-beta.1",
4
4
  "description": "MindedJS is a TypeScript library for building agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -72,4 +72,4 @@
72
72
  "peerDependencies": {
73
73
  "playwright": "^1.55.0"
74
74
  }
75
- }
75
+ }
package/src/agent.ts CHANGED
@@ -490,6 +490,21 @@ export class Agent {
490
490
  } catch (err) {
491
491
  const { baseUrl } = getConfig();
492
492
  logger.error({ msg: '[Trigger] Agent initialization failed', err, sessionId, baseUrl });
493
+
494
+ // Notify backend about the initialization error
495
+ if (mindedConnection.isConnected()) {
496
+ try {
497
+ await mindedConnection.emit(mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR, {
498
+ type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR,
499
+ sessionId,
500
+ error: err instanceof Error ? err.message : String(err),
501
+ stack: err instanceof Error ? err.stack : undefined,
502
+ });
503
+ } catch (emitErr) {
504
+ logger.error({ msg: '[Agent] Failed to emit initialization error to backend', emitErr });
505
+ }
506
+ }
507
+
493
508
  throw err;
494
509
  }
495
510
 
@@ -644,6 +659,20 @@ export class Agent {
644
659
  // Release the session lock on error
645
660
  await this.interruptSessionManager.release(sessionId);
646
661
 
662
+ // Notify backend about the error
663
+ if (mindedConnection.isConnected()) {
664
+ try {
665
+ await mindedConnection.emit(mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR, {
666
+ type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR,
667
+ sessionId,
668
+ error: err instanceof Error ? err.message : String(err),
669
+ stack: err instanceof Error ? err.stack : undefined,
670
+ });
671
+ } catch (emitErr) {
672
+ logger.error({ msg: '[Agent] Failed to emit error to backend', emitErr });
673
+ }
674
+ }
675
+
647
676
  const state = await this.compiledGraph.getState(this.getLangraphConfig(sessionId));
648
677
  const results = await this.emit(AgentEvents.ERROR, {
649
678
  error: err instanceof Error ? err : new Error(JSON.stringify(err)),
@@ -854,7 +883,7 @@ export class Agent {
854
883
  } else if (runLocally) {
855
884
  throw new Error(
856
885
  'No LLM configuration found in minded.json. When running locally, you must provide LLM configuration in minded.json. ' +
857
- 'Add an "llm" section to your minded.json file with provider and model settings.',
886
+ 'Add an "llm" section to your minded.json file with provider and model settings.',
858
887
  );
859
888
  } else {
860
889
  throw new Error('No LLM configuration found. Please configure LLM in Organization Settings or add it to minded.json');
@@ -1224,7 +1253,7 @@ export class Agent {
1224
1253
  /**
1225
1254
  * Handle tool execution mode when agent is run with 'runTool' argument
1226
1255
  * This allows running tools directly via: npm run tool <toolName> [params...] [cookies=false]
1227
- *
1256
+ *
1228
1257
  * @private
1229
1258
  */
1230
1259
  private async handleToolExecutionMode(): Promise<void> {
@@ -1252,12 +1281,15 @@ export class Agent {
1252
1281
  flags,
1253
1282
  });
1254
1283
 
1255
- const result = await this.executeTool({
1256
- toolName,
1257
- toolParams: params,
1258
- sessionId: uuidv4(),
1259
- requestId: uuidv4()
1260
- }, 'direct');
1284
+ const result = await this.executeTool(
1285
+ {
1286
+ toolName,
1287
+ toolParams: params,
1288
+ sessionId: uuidv4(),
1289
+ requestId: uuidv4(),
1290
+ },
1291
+ 'direct',
1292
+ );
1261
1293
 
1262
1294
  if (result.error) {
1263
1295
  logger.error({
@@ -43,26 +43,26 @@ const loadCookiesForSession = async (sessionId: string): Promise<void> => {
43
43
  // Check if cookies should be disabled
44
44
  const noCookies = process.env.MINDED_NO_COOKIES === 'true';
45
45
  if (noCookies) {
46
- logger.info({ message: 'Cookies disabled via cookies=false flag', sessionId });
46
+ logger.info({ message: '[Browser] Cookies disabled via cookies=false flag', sessionId });
47
47
  return;
48
48
  }
49
49
 
50
50
  const stored = await cookieStore.load();
51
51
  if (!stored || stored.length === 0) {
52
- logger.info({ message: 'No stored cookies to load', sessionId });
52
+ logger.info({ message: '[Browser] No stored cookies to load', sessionId });
53
53
  return;
54
54
  }
55
55
  const cdp = cdpClients.get(sessionId);
56
56
  if (!cdp) {
57
- logger.warn({ message: 'No CDP client found for session', sessionId });
57
+ logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
58
58
  return;
59
59
  }
60
60
 
61
61
  try {
62
62
  await cdp.Network.setCookies({ cookies: stored });
63
- logger.info({ message: 'Loaded cookies into browser session', sessionId, count: stored.length });
63
+ logger.info({ message: '[Browser] Loaded cookies into browser session', sessionId, count: stored.length });
64
64
  } catch (error) {
65
- logger.warn({ message: 'Failed to set cookies via CDP', sessionId, error });
65
+ logger.warn({ message: '[Browser] Failed to set cookies via CDP', sessionId, error });
66
66
  }
67
67
  };
68
68
 
@@ -77,19 +77,19 @@ const saveCookiesForSession = async (sessionId: string): Promise<void> => {
77
77
  try {
78
78
  const cdp = cdpClients.get(sessionId);
79
79
  if (!cdp) {
80
- logger.warn({ message: 'No CDP client found for session', sessionId });
80
+ logger.warn({ message: '[Browser] No CDP client found for session', sessionId });
81
81
  return;
82
82
  }
83
83
 
84
84
  const result = await cdp.Network.getAllCookies();
85
85
  if (result) {
86
86
  await cookieStore.save(result.cookies);
87
- logger.info({ message: 'Saved cookies from browser session', sessionId, count: result.cookies.length });
87
+ logger.info({ message: '[Browser] Saved cookies from browser session', sessionId, count: result.cookies.length });
88
88
  } else {
89
- logger.warn({ message: 'No cookies found in browser session', sessionId });
89
+ logger.warn({ message: '[Browser] No cookies found in browser session', sessionId });
90
90
  }
91
91
  } catch (error) {
92
- logger.warn({ message: 'Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
92
+ logger.warn({ message: '[Browser] Failed to get/save cookies from session', sessionId, error: JSON.stringify(error) });
93
93
  }
94
94
  };
95
95
 
@@ -100,7 +100,7 @@ const cleanupCdpClient = async (sessionId: string): Promise<void> => {
100
100
  try {
101
101
  await cdp.close();
102
102
  } catch (error) {
103
- logger.warn({ message: 'Failed to close CDP client', sessionId, error });
103
+ logger.warn({ message: '[Browser] Failed to close CDP client', sessionId, error });
104
104
  }
105
105
  cdpClients.delete(sessionId);
106
106
  }
@@ -116,9 +116,8 @@ export const createBrowserSession = async ({
116
116
  proxy?: string;
117
117
  browserTaskMode: BrowserTaskMode;
118
118
  }): Promise<CreateBrowserSessionResponse> => {
119
- logger.debug({ msg: 'Creating browser session via socket', proxy });
120
-
121
- if (browserTaskMode === BrowserTaskMode.LOCAL) {
119
+ if (browserTaskMode === BrowserTaskMode.LOCAL && process.env.MINDED_CLOUD !== 'true') {
120
+ logger.debug({ msg: '[Browser] Creating local browser session', proxy });
122
121
  const { cdpUrl, instanceId } = await getOrStartLocalCDP({
123
122
  headless: false,
124
123
  });
@@ -128,19 +127,19 @@ export const createBrowserSession = async ({
128
127
 
129
128
  const port = extractPortFromCdpUrl(cdpUrl);
130
129
  if (!port) {
131
- logger.warn({ message: 'Failed to extract port from CDP URL', sessionId, cdpUrl });
130
+ logger.warn({ message: '[Browser] Failed to extract port from CDP URL', sessionId, cdpUrl });
132
131
  } else {
133
132
  const cdp = await CDP({ host: 'localhost', port });
134
133
  await cdp.Network.enable();
135
134
  cdpClients.set(sessionId, cdp);
136
- logger.info({ message: 'Created CDP client for session', sessionId, port });
135
+ logger.info({ message: '[Browser] Created CDP client for session', sessionId, port });
137
136
  }
138
137
 
139
138
  // Load cookies for this session (best-effort)
140
139
  try {
141
140
  await loadCookiesForSession(sessionId);
142
141
  } catch (error) {
143
- logger.warn({ message: 'Failed to load cookies for session', sessionId, error });
142
+ logger.warn({ message: '[Browser] Failed to load cookies for session', sessionId, error });
144
143
  }
145
144
 
146
145
  return {
@@ -149,6 +148,7 @@ export const createBrowserSession = async ({
149
148
  };
150
149
  }
151
150
 
151
+ logger.debug({ msg: '[Browser] Creating browser session via socket', proxy });
152
152
  const response = await mindedConnection.awaitEmit<CreateBrowserSessionRequest, CreateBrowserSessionResponse>(
153
153
  mindedConnectionSocketMessageType.CREATE_BROWSER_SESSION,
154
154
  {
@@ -160,12 +160,12 @@ export const createBrowserSession = async ({
160
160
  );
161
161
 
162
162
  if (response.error) {
163
- logger.error({ msg: 'Failed to create browser session', error: response.error });
163
+ logger.error({ msg: '[Browser] Failed to create browser session', error: response.error });
164
164
  throw new Error(response.error);
165
165
  }
166
166
 
167
167
  logger.debug({
168
- msg: 'Browser session created successfully',
168
+ msg: '[Browser] Browser session created successfully',
169
169
  sessionId: response.sessionId,
170
170
  hasLiveUrl: !!response.liveViewUrl,
171
171
  });
@@ -182,7 +182,7 @@ export const destroyBrowserSession = async ({
182
182
  onPrem?: boolean;
183
183
  localRun?: boolean;
184
184
  }): Promise<DestroyBrowserSessionResponse> => {
185
- logger.debug({ msg: 'Destroying browser session via socket', sessionId, onPrem });
185
+ logger.debug({ msg: '[Browser] Destroying browser session via socket', sessionId, onPrem });
186
186
 
187
187
  if (localRun || process.env.BROWSER_TASK_MODE === BrowserTaskMode.LOCAL) {
188
188
  // Kill specific instance if we have the mapping
@@ -192,7 +192,7 @@ export const destroyBrowserSession = async ({
192
192
  localSessionInstances.delete(sessionId);
193
193
  } else {
194
194
  // Fallback to killing all if no specific instance found
195
- logger.warn({ msg: 'No instance ID found for session, killing all instances', sessionId });
195
+ logger.warn({ msg: '[Browser] No instance ID found for session, killing all instances', sessionId });
196
196
  await kill();
197
197
  }
198
198
  return {
@@ -211,11 +211,11 @@ export const destroyBrowserSession = async ({
211
211
  );
212
212
 
213
213
  if (response.error) {
214
- logger.error({ msg: 'Failed to destroy browser session', error: response.error, sessionId });
214
+ logger.error({ msg: '[Browser] Failed to destroy browser session', error: response.error, sessionId });
215
215
  throw new Error(response.error);
216
216
  }
217
217
 
218
- logger.debug({ msg: 'Browser session destroyed successfully', sessionId });
218
+ logger.debug({ msg: '[Browser] Browser session destroyed successfully', sessionId });
219
219
  return response;
220
220
  };
221
221
 
@@ -237,7 +237,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
237
237
 
238
238
  const args = ['run', pythonScriptPath];
239
239
  logger.info({
240
- message: 'Spawning Python process',
240
+ message: '[Browser] Spawning Python process',
241
241
  args,
242
242
  });
243
243
 
@@ -286,7 +286,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
286
286
  const interval = setInterval(() => {
287
287
  // Check if chromium process stopped running
288
288
  if (!isLocalBrowserRunning()) {
289
- logger.error({ message: 'Local browser process stopped running, killing browser task' });
289
+ logger.error({ message: '[Browser] Local browser process stopped running, killing browser task' });
290
290
  child.kill();
291
291
  clearInterval(interval);
292
292
  }
@@ -305,18 +305,18 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
305
305
  clearInterval(interval);
306
306
 
307
307
  if (exitCode !== 0) {
308
- logger.error({ message: 'Operator failed', exitCode, stderr: stderrBuffer });
308
+ logger.error({ message: '[Browser] Operator failed', exitCode, stderr: stderrBuffer });
309
309
  throw new Error(`Local browser task failed with exit code ${exitCode}`);
310
310
  }
311
311
 
312
- logger.info({ message: 'Operator finished' });
312
+ logger.info({ message: '[Browser] Operator finished' });
313
313
 
314
314
  let result = stdoutBuffer.split('___RESULT___')[1]?.trim() || stdoutBuffer;
315
315
  if (outputSchema?.length) {
316
316
  try {
317
317
  result = JSON.parse(result);
318
318
  } catch (error) {
319
- logger.debug({ message: 'Failed to parse result', error });
319
+ logger.debug({ message: '[Browser] Failed to parse result', error });
320
320
  }
321
321
  }
322
322
 
@@ -326,9 +326,9 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
326
326
  try {
327
327
  const files = readdirSync(folderPath);
328
328
  downloadedFiles = files.map((file) => path.join(folderPath, file));
329
- logger.debug({ message: 'Found downloaded files', count: downloadedFiles.length, files: downloadedFiles });
329
+ logger.debug({ message: '[Browser] Found downloaded files', count: downloadedFiles.length, files: downloadedFiles });
330
330
  } catch (error) {
331
- logger.error({ message: 'Failed to read downloads folder', error });
331
+ logger.error({ message: '[Browser] Failed to read downloads folder', error });
332
332
  }
333
333
  }
334
334
 
@@ -336,7 +336,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
336
336
  try {
337
337
  await saveCookiesForSession(sessionId);
338
338
  } catch (error) {
339
- logger.warn({ message: 'Failed to save cookies after task completion', sessionId, error });
339
+ logger.warn({ message: '[Browser] Failed to save cookies after task completion', sessionId, error });
340
340
  }
341
341
 
342
342
  return {
@@ -354,7 +354,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
354
354
  }
355
355
 
356
356
  logger.debug({
357
- msg: 'Invoking browser task via socket',
357
+ msg: '[Browser] Invoking browser task via socket',
358
358
  sessionId,
359
359
  taskLength: task.length,
360
360
  keepAlive,
@@ -380,12 +380,12 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
380
380
  );
381
381
 
382
382
  if (response.error) {
383
- logger.error({ msg: 'Failed to invoke browser task', error: response.error });
383
+ logger.error({ msg: '[Browser] Failed to invoke browser task', error: response.error });
384
384
  throw new Error(response.error);
385
385
  }
386
386
 
387
387
  logger.debug({
388
- msg: 'Browser task completed successfully',
388
+ msg: '[Browser] Browser task completed successfully',
389
389
  sessionId,
390
390
  hasResult: !!response.result,
391
391
  stepCount: response.steps?.length || 0,
@@ -394,7 +394,7 @@ export const invokeBrowserTask = async (options: InvokeBrowserTaskOptions): Prom
394
394
 
395
395
  return response;
396
396
  } catch (err) {
397
- logger.error({ message: 'Error invoking browser task', err });
397
+ logger.error({ message: '[Browser] Error invoking browser task', err });
398
398
  throw err;
399
399
  }
400
400
  };
@@ -95,7 +95,7 @@ export const addPromptNode = async ({ graph, node, llm, tools, emit, agent }: Ad
95
95
  });
96
96
 
97
97
  return toolResult;
98
- }
98
+ },
99
99
  );
100
100
 
101
101
  return result;
@@ -13,6 +13,8 @@ import { createHistoryStep } from '../utils/history';
13
13
  import { compilePrompt } from './compilePrompt';
14
14
  import { AnalyticsEventName } from '../types/Analytics.types';
15
15
  import { trackAnalyticsEvent } from '../internalTools/analytics';
16
+ import { convertArraySchemaToZod } from '../utils/schemaConverter';
17
+ import { v4 as uuidv4 } from 'uuid';
16
18
 
17
19
  type AddThinkingNodeParams = {
18
20
  graph: PreCompiledGraph;
@@ -51,8 +53,17 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
51
53
  const messagesForModel = [...state.messages];
52
54
  messagesForModel.push(systemMessage);
53
55
 
56
+ // Check if we need structured output
57
+ const useStructuredOutput = node.outputSchema && node.outputSchema.length > 0;
58
+ let llmWithStructuredOutput = llmToUse;
59
+
60
+ if (useStructuredOutput) {
61
+ const zodSchema = convertArraySchemaToZod(node.outputSchema!);
62
+ llmWithStructuredOutput = llmToUse.withStructuredOutput(zodSchema, { name: 'extract' });
63
+ }
64
+
54
65
  const startTime = Date.now();
55
- const result: AIMessage = await llmToUse.invoke(messagesForModel);
66
+ const result = await llmWithStructuredOutput.invoke(messagesForModel);
56
67
  const endTime = Date.now();
57
68
 
58
69
  logger.debug({
@@ -65,17 +76,35 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
65
76
  await agent.interruptSessionManager.checkQueueAndInterrupt(state.sessionId, state);
66
77
 
67
78
  // Inject metadata to identify this as a thinking node output
68
- const messageWithMetadata = new AIMessage({
69
- content: result.content,
70
- additional_kwargs: {
71
- ...result.additional_kwargs,
72
- mindedMetadata: {
73
- nodeType: NodeType.THINKING,
74
- nodeDisplayName: node.displayName,
75
- prompt: node.prompt,
76
- },
79
+ const additionalKwargs: Record<string, any> = {
80
+ mindedMetadata: {
81
+ nodeType: NodeType.THINKING,
82
+ nodeDisplayName: node.displayName,
83
+ prompt: node.prompt,
77
84
  },
78
- id: result.id,
85
+ };
86
+
87
+ let messageId: string;
88
+ let messageContent: string;
89
+
90
+ // When structured output is used, result is the raw extracted data (not an AIMessage)
91
+ // When not used, result is an AIMessage with content and id
92
+ if (useStructuredOutput) {
93
+ additionalKwargs.structuredOutput = result;
94
+ messageId = uuidv4();
95
+ messageContent = JSON.stringify(result);
96
+ } else {
97
+ // result is an AIMessage, preserve its additional_kwargs and id
98
+ const aiResult = result as AIMessage;
99
+ Object.assign(additionalKwargs, aiResult.additional_kwargs);
100
+ messageId = aiResult.id!;
101
+ messageContent = aiResult.content as string;
102
+ }
103
+
104
+ const messageWithMetadata = new AIMessage({
105
+ content: messageContent,
106
+ additional_kwargs: additionalKwargs,
107
+ id: messageId,
79
108
  });
80
109
 
81
110
  // Add to state messages
@@ -100,4 +129,3 @@ export const addThinkingNode = async ({ graph, node, llm, agent }: AddThinkingNo
100
129
  };
101
130
  graph.addNode(node.name, callback);
102
131
  };
103
-
@@ -1,5 +1,69 @@
1
1
  import * as ejs from 'ejs';
2
2
  import { logger } from '../utils/logger';
3
+ import { HistoryStep } from '../types/Agent.types';
4
+
5
+ /**
6
+ * Extract node outputs from state.history and state.messages
7
+ * Returns a map of nodeId/nodeDisplayName -> output object
8
+ */
9
+ function extractNodeOutputs(state: any): Record<string, any> {
10
+ if (!state?.history || !state?.messages) {
11
+ return {};
12
+ }
13
+
14
+ const outputs: Record<string, any> = {};
15
+
16
+ // Build lookup maps by message type
17
+ const messagesByType = {
18
+ tool: new Map(
19
+ state.messages
20
+ .filter((msg: any) => msg.constructor?.name === 'ToolMessage' && msg.tool_call_id)
21
+ .map((msg: any) => [msg.tool_call_id, msg]),
22
+ ),
23
+ ai: new Map(state.messages.filter((msg: any) => msg.constructor?.name === 'AIMessage' && msg.id).map((msg: any) => [msg.id, msg])),
24
+ };
25
+
26
+ const setOutput = (item: HistoryStep, value: any) => {
27
+ if (item.nodeId) outputs[item.nodeId] = value;
28
+ if (item.nodeDisplayName) outputs[item.nodeDisplayName] = value;
29
+ };
30
+
31
+ const extractors: Record<string, (item: HistoryStep) => void> = {
32
+ tool: (item) => {
33
+ const toolCallId = item.messageIds?.[1] ?? item.messageIds?.[0];
34
+ const toolMessage: any = messagesByType.tool.get(toolCallId);
35
+ if (!toolMessage) return;
36
+
37
+ try {
38
+ const content = typeof toolMessage.content === 'string' ? JSON.parse(toolMessage.content) : toolMessage.content;
39
+ setOutput(item, content?.result ?? content);
40
+ } catch (err) {
41
+ logger.debug({ message: 'Failed to parse tool message content', toolCallId, err });
42
+ }
43
+ },
44
+
45
+ thinking: (item) => {
46
+ const messageId = item.messageIds?.[0];
47
+ if (!messageId) return;
48
+
49
+ const aiMessage: any = messagesByType.ai.get(messageId);
50
+ if (!aiMessage) return;
51
+
52
+ const structuredOutput = aiMessage.additional_kwargs?.structuredOutput;
53
+
54
+ if (structuredOutput) {
55
+ setOutput(item, structuredOutput);
56
+ }
57
+ },
58
+ };
59
+
60
+ for (const item of state.history) {
61
+ if (!item.messageIds?.length) continue;
62
+ extractors[item.type]?.(item);
63
+ }
64
+
65
+ return outputs;
66
+ }
3
67
 
4
68
  /**
5
69
  * Compile prompt with parameters using EJS and placeholder replacement
@@ -9,6 +73,17 @@ export function compilePrompt(prompt: string, params: Record<string, any> = {}):
9
73
  // Define system parameters
10
74
  params.system = { currentTime: new Date().toISOString() };
11
75
 
76
+ // Extract node outputs from state if available
77
+ // This enables placeholder replacement like {tools.NodeName.field} or {NodeName.field}
78
+ // by making previous node outputs available in the params object.
79
+ // Example: If ExtractUser node outputs { userName: "Bob", userAge: 35 },
80
+ // the prompt "Hello {tools.ExtractUser.userName}" becomes "Hello Bob"
81
+ if (params.state) {
82
+ const nodeOutputs = extractNodeOutputs(params.state);
83
+ // Merge node outputs into params so they're accessible during placeholder replacement
84
+ params = { ...params, ...nodeOutputs };
85
+ }
86
+
12
87
  // First, render with EJS
13
88
  let compiledPrompt = ejs.render(prompt, params);
14
89
 
@@ -17,24 +92,37 @@ export function compilePrompt(prompt: string, params: Record<string, any> = {}):
17
92
 
18
93
  return compiledPrompt;
19
94
  } catch (err) {
20
- logger.error({ message: 'Error compiling prompt', err });
95
+ logger.error({ message: 'Error compiling prompt', err, prompt });
21
96
  return prompt; // Return uncompiled if there's an error
22
97
  }
23
98
  }
24
99
 
25
100
  /**
26
101
  * Replace placeholders in {key} format
102
+ * Supports both:
103
+ * - {NodeName.field} - direct node reference
104
+ * - {tools.NodeName.field} - node reference with 'tools' prefix (alias)
27
105
  */
28
106
  function replacePlaceholders(text: string, params: Record<string, any>): string {
29
107
  return text.replace(/\{([^}]+)\}/g, (match, key) => {
30
- const keys = key.split('.');
108
+ let keys = key.split('.');
109
+
110
+ // Handle 'tools.' prefix as an alias for node outputs
111
+ // {tools.NodeName.field} -> {NodeName.field}
112
+ if (keys[0] === 'tools' && keys.length >= 3) {
113
+ keys = keys.slice(1); // Remove 'tools' prefix
114
+ }
115
+
31
116
  let value: any = params;
32
117
 
33
- for (const k of keys) {
118
+ for (let i = 0; i < keys.length; i++) {
119
+ const k = keys[i];
34
120
  if (value && typeof value === 'object' && k in value) {
35
121
  value = value[k];
36
122
  } else {
37
- logger.warn({ message: `Placeholder {${key}} in prompt not found in params. It will remain as placeholder.` });
123
+ logger.warn({
124
+ message: `Placeholder {${key}} in prompt not found in params. It will remain as placeholder.`,
125
+ });
38
126
  return match; // Return original if key not found
39
127
  }
40
128
  }
@@ -72,6 +72,7 @@ export enum mindedConnectionSocketMessageType {
72
72
  RPA_LOGS_UPLOAD = 'rpa-logs-upload',
73
73
  // RPA Error Upload
74
74
  RPA_ERROR_UPLOAD = 'rpa-error-upload',
75
+ ON_AGENT_RUN_ERROR = 'on-agent-run-error',
75
76
  }
76
77
 
77
78
  export type mindedConnectionSocketMessageTypeMap = {
@@ -125,6 +126,7 @@ export type mindedConnectionSocketMessageTypeMap = {
125
126
  [mindedConnectionSocketMessageType.RPA_SCREENSHOT_UPLOAD]: RPAScreenshotUploadRequest;
126
127
  [mindedConnectionSocketMessageType.RPA_LOGS_UPLOAD]: RPALogsUploadRequest;
127
128
  [mindedConnectionSocketMessageType.RPA_ERROR_UPLOAD]: RPAErrorUploadRequest;
129
+ [mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR]: OnAgentRunErrorRequest;
128
130
  };
129
131
 
130
132
  export interface BasemindedConnectionSocketMessage {
@@ -633,3 +635,14 @@ export interface RPAErrorUploadResponse extends BaseSdkConnectionSocketMessageRe
633
635
  screenshotS3Url?: string;
634
636
  htmlS3Url?: string;
635
637
  }
638
+
639
+ export interface OnAgentRunErrorRequest extends BasemindedConnectionSocketMessage {
640
+ type: mindedConnectionSocketMessageType.ON_AGENT_RUN_ERROR;
641
+ sessionId: string;
642
+ error: string;
643
+ stack?: string;
644
+ }
645
+
646
+ export interface OnAgentRunErrorResponse extends BaseSdkConnectionSocketMessageResponseCallbackAck {
647
+ success?: boolean;
648
+ }