@atomoz/workflows-nodes 0.1.26 → 0.1.28

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.
package/dist/index.js CHANGED
@@ -1008,7 +1008,7 @@ async function createLLMFromModel(modelConfig, authToken, streaming = false) {
1008
1008
  case "gemini":
1009
1009
  return new ChatGoogle({
1010
1010
  model: "gemini-flash-latest",
1011
- apiKey: "AIzaSyBzrL8Hx6dHhXgwc2HfLlQsf5Y-9pdtc9M",
1011
+ apiKey: "AIzaSyAWj0Al2sVJ8vqaRN4UY7c6imAg7a3NbEc",
1012
1012
  streaming
1013
1013
  });
1014
1014
  case "openai":
@@ -1034,6 +1034,62 @@ async function createLLMFromModel(modelConfig, authToken, streaming = false) {
1034
1034
  }
1035
1035
 
1036
1036
  // src/utils/guardrail-executor.ts
1037
+ function emitGuardrailEvent(context, name, passed, action, details) {
1038
+ if (context?.emitter?.emitThought) {
1039
+ context.emitter.emitThought({
1040
+ content: `guardrail:${passed ? "passed" : "failed"}:${name}`,
1041
+ type: "guardrail",
1042
+ name,
1043
+ passed,
1044
+ action: action || "evaluate",
1045
+ details
1046
+ });
1047
+ }
1048
+ }
1049
+ async function loadChatHistory(context) {
1050
+ const checkpointer = context?.checkpointer;
1051
+ const sessionId = context?.sessionId;
1052
+ if (!checkpointer || !sessionId) {
1053
+ console.log(`[GUARDRAIL] checkpointer=${!!checkpointer}, sessionId=${sessionId}`);
1054
+ return null;
1055
+ }
1056
+ try {
1057
+ console.log(`[GUARDRAIL] Carregando hist\xF3rico da mem\xF3ria (session: ${sessionId})`);
1058
+ if (typeof checkpointer.setup === "function" && !checkpointer.isSetup) {
1059
+ await checkpointer.setup();
1060
+ }
1061
+ let checkpoint = null;
1062
+ if (typeof checkpointer.getTuple === "function") {
1063
+ const tuple = await checkpointer.getTuple({ configurable: { thread_id: sessionId } });
1064
+ checkpoint = tuple?.checkpoint;
1065
+ console.log(`[GUARDRAIL] getTuple result:`, tuple ? "found" : "null");
1066
+ }
1067
+ if (!checkpoint && typeof checkpointer.get === "function") {
1068
+ checkpoint = await checkpointer.get({ configurable: { thread_id: sessionId } });
1069
+ console.log(`[GUARDRAIL] get result:`, checkpoint ? "found" : "null");
1070
+ }
1071
+ if (!checkpoint) {
1072
+ console.log(`[GUARDRAIL] Nenhum checkpoint encontrado`);
1073
+ return null;
1074
+ }
1075
+ console.log(`[GUARDRAIL] Checkpoint keys:`, Object.keys(checkpoint));
1076
+ const messages = checkpoint?.channel_values?.messages || checkpoint?.values?.messages || checkpoint?.messages || [];
1077
+ if (!messages || messages.length === 0) {
1078
+ console.log(`[GUARDRAIL] Nenhuma mensagem no checkpoint`);
1079
+ return null;
1080
+ }
1081
+ const historyLines = messages.map((m) => {
1082
+ const role = m._getType?.() === "human" || m.type === "human" ? "Usu\xE1rio" : "Assistente";
1083
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
1084
+ return `${role}: ${content.slice(0, 200)}${content.length > 200 ? "..." : ""}`;
1085
+ });
1086
+ console.log(`[GUARDRAIL] Hist\xF3rico carregado: ${messages.length} mensagens`);
1087
+ return historyLines.join("\n");
1088
+ } catch (error) {
1089
+ console.error(`[GUARDRAIL] Erro ao carregar hist\xF3rico:`, error);
1090
+ return null;
1091
+ }
1092
+ }
1037
1093
  async function executeGuardrails(guardrails, content, mode = "fail-first", context = {}) {
1038
1094
  const list = Array.isArray(guardrails) ? guardrails : [guardrails];
1039
1095
  const result = {
@@ -1084,6 +1140,7 @@ async function executeGuardrails(guardrails, content, mode = "fail-first", conte
1084
1140
  if (!guardPassed && actionInstruction) {
1085
1141
  violationMessage = actionInstruction;
1086
1142
  }
1143
+ emitGuardrailEvent(context, guard.name, guardPassed, guard.action, violationMessage || "Rule passed");
1087
1144
  } else if (guard.type === "function") {
1088
1145
  const { nodeFunction, name, actionInstruction } = guard;
1089
1146
  if (typeof nodeFunction === "function") {
@@ -1116,47 +1173,97 @@ async function executeGuardrails(guardrails, content, mode = "fail-first", conte
1116
1173
  guardPassed = false;
1117
1174
  violationMessage = `Erro na fun\xE7\xE3o guardrail: ${error instanceof Error ? error.message : String(error)}`;
1118
1175
  }
1176
+ emitGuardrailEvent(context, name, guardPassed, guard.action, violationMessage || "Function passed");
1119
1177
  }
1120
1178
  } else if (guard.type === "model") {
1121
1179
  const { model, evaluationPrompt, name, actionInstruction } = guard;
1180
+ console.log(`[GUARDRAIL LLM] Iniciando guardrail "${name}"`);
1181
+ console.log(`[GUARDRAIL LLM] Conte\xFAdo a avaliar (${content.length} chars):`, content.slice(0, 200) + (content.length > 200 ? "..." : ""));
1182
+ console.log(`[GUARDRAIL LLM] Action: ${guard.action}, Model dispon\xEDvel: ${!!model}`);
1183
+ const chatHistory = await loadChatHistory(context);
1184
+ if (chatHistory) {
1185
+ console.log(`[GUARDRAIL LLM] Hist\xF3rico inclu\xEDdo no contexto`);
1186
+ }
1122
1187
  if (model && typeof model.invoke === "function") {
1123
1188
  try {
1124
1189
  let prompt = evaluationPrompt;
1190
+ console.log(`[GUARDRAIL LLM] Evaluation prompt base:`, (prompt || "").slice(0, 200));
1125
1191
  if (actionInstruction) {
1126
1192
  prompt += `
1127
1193
 
1128
1194
  INSTRU\xC7\xC3O ADICIONAL DE A\xC7\xC3O:
1129
1195
  ${actionInstruction}`;
1196
+ }
1197
+ if (chatHistory) {
1198
+ prompt += `
1199
+
1200
+ HIST\xD3RICO DA CONVERSA:
1201
+ ${chatHistory}`;
1130
1202
  }
1131
1203
  if (prompt.includes("{{content}}")) {
1132
1204
  prompt = prompt.replace("{{content}}", content);
1133
1205
  } else {
1134
1206
  prompt = `${prompt}
1135
1207
 
1136
- CONTE\xDADO PARA AVALIAR:
1208
+ CONTE\xDADO A AVALIAR (resposta atual):
1137
1209
  ${content}`;
1138
1210
  }
1211
+ console.log(`[GUARDRAIL LLM] Chamando modelo...`);
1139
1212
  const response = await model.invoke(prompt);
1140
1213
  const resultText = typeof response.content === "string" ? response.content : String(response.content);
1214
+ console.log(`[GUARDRAIL LLM] Resposta do modelo:`, resultText.slice(0, 300));
1215
+ const isUnsafe = resultText.toUpperCase().includes("UNSAFE");
1216
+ const isSafe = resultText.toUpperCase().startsWith("SAFE") || resultText.toUpperCase().includes("SAFE") && !isUnsafe;
1141
1217
  if (guard.action === "fix") {
1142
- if (resultText && resultText.trim() !== content.trim() && !resultText.includes("SAFE")) {
1143
- result.modifiedContent = resultText;
1144
- violationMessage = `Conte\xFAdo reescrito pelo guardrail "${name}".`;
1145
- result.violations.push({
1146
- name: guard.name,
1147
- message: violationMessage,
1148
- action: "fix"
1149
- });
1218
+ if (isUnsafe) {
1219
+ console.log(`[GUARDRAIL LLM] Modo FIX - Detectado UNSAFE, chamando modelo para corrigir...`);
1220
+ const fixPrompt = `Voc\xEA \xE9 um corretor de conte\xFAdo. O texto abaixo foi marcado como UNSAFE por violar uma regra.
1221
+
1222
+ REGRA VIOLADA:
1223
+ ${evaluationPrompt}
1224
+
1225
+ ${actionInstruction ? `INSTRU\xC7\xC3O DE CORRE\xC7\xC3O:
1226
+ ${actionInstruction}
1227
+ ` : ""}
1228
+
1229
+ CONTE\xDADO ORIGINAL:
1230
+ ${content}
1231
+
1232
+ Reescreva o conte\xFAdo corrigindo a viola\xE7\xE3o. Retorne APENAS o texto corrigido, sem explica\xE7\xF5es ou prefixos.`;
1233
+ try {
1234
+ const fixResponse = await model.invoke(fixPrompt);
1235
+ const fixedContent = typeof fixResponse.content === "string" ? fixResponse.content : String(fixResponse.content);
1236
+ console.log(`[GUARDRAIL LLM] Conte\xFAdo corrigido:`, fixedContent.slice(0, 300));
1237
+ result.modifiedContent = fixedContent;
1238
+ violationMessage = `Conte\xFAdo reescrito pelo guardrail "${name}".`;
1239
+ result.violations.push({
1240
+ name: guard.name,
1241
+ message: violationMessage,
1242
+ action: "fix"
1243
+ });
1244
+ emitGuardrailEvent(context, name, false, "fix", `Content fixed: ${fixedContent.slice(0, 100)}...`);
1245
+ } catch (fixError) {
1246
+ console.error(`[GUARDRAIL LLM] Erro ao corrigir conte\xFAdo:`, fixError);
1247
+ }
1248
+ } else {
1249
+ console.log(`[GUARDRAIL LLM] Modo FIX - Conte\xFAdo aprovado (SAFE)`);
1150
1250
  }
1151
1251
  } else {
1152
- if (resultText.toUpperCase().includes("UNSAFE")) {
1252
+ if (isUnsafe) {
1153
1253
  guardPassed = false;
1154
1254
  violationMessage = actionInstruction || `Modelo de avalia\xE7\xE3o "${name}" detectou viola\xE7\xE3o de seguran\xE7a/pol\xEDtica.`;
1255
+ console.log(`[GUARDRAIL LLM] Resultado: UNSAFE - Bloqueado`);
1256
+ emitGuardrailEvent(context, name, false, guard.action, violationMessage);
1257
+ } else {
1258
+ console.log(`[GUARDRAIL LLM] Resultado: SAFE - Aprovado`);
1259
+ emitGuardrailEvent(context, name, true, "approve", "Content passed evaluation");
1155
1260
  }
1156
1261
  }
1157
1262
  } catch (error) {
1158
- console.error(`Error executing model guardrail "${name}":`, error);
1263
+ console.error(`[GUARDRAIL LLM] Erro ao executar guardrail "${name}":`, error);
1159
1264
  }
1265
+ } else {
1266
+ console.log(`[GUARDRAIL LLM] Model n\xE3o dispon\xEDvel ou n\xE3o \xE9 invoc\xE1vel para guardrail "${name}"`);
1160
1267
  }
1161
1268
  }
1162
1269
  if (!guardPassed) {
@@ -1220,7 +1327,7 @@ var IaAgentNodeFunction = async (inputs) => {
1220
1327
  let guardrailViolations = [];
1221
1328
  if (message && beforeGuardrails) {
1222
1329
  const preparedBefore = await prepareGuardrails(beforeGuardrails);
1223
- const context = { ...inputs, input: message };
1330
+ const context = { ...inputs, input: message, checkpointer, sessionId };
1224
1331
  const beforeResults = await executeGuardrails(preparedBefore, message, guardrailMode, context);
1225
1332
  if (beforeResults.blocked) {
1226
1333
  console.log(`\u{1F6E1}\uFE0F IaAgentNode "${name}": Blocked by before-agent guardrail:`, beforeResults.message);
@@ -1422,7 +1529,7 @@ IMPORTANT: You must base your response on the last message in the conversation h
1422
1529
  }
1423
1530
  if (output && afterGuardrails) {
1424
1531
  const preparedAfter = await prepareGuardrails(afterGuardrails);
1425
- const context = { ...inputs, input: output };
1532
+ const context = { ...inputs, input: output, checkpointer, sessionId };
1426
1533
  const afterResults = await executeGuardrails(preparedAfter, output, guardrailMode, context);
1427
1534
  if (afterResults.blocked) {
1428
1535
  console.log(`\u{1F6E1}\uFE0F IaAgentNode "${name}": Response blocked by after-agent guardrail:`, afterResults.message);
@@ -2311,8 +2418,271 @@ var WhatsappMessageTriggerNode = {
2311
2418
  ]
2312
2419
  };
2313
2420
 
2421
+ // src/utils/sandbox-executor.ts
2422
+ var ivmModule = null;
2423
+ async function getIVM() {
2424
+ if (ivmModule !== null) return ivmModule;
2425
+ try {
2426
+ const mod = await import("isolated-vm");
2427
+ ivmModule = {
2428
+ Isolate: mod.Isolate || mod.default?.Isolate,
2429
+ ExternalCopy: mod.ExternalCopy || mod.default?.ExternalCopy,
2430
+ Reference: mod.Reference || mod.default?.Reference
2431
+ };
2432
+ if (!ivmModule.Isolate) {
2433
+ console.warn("[Sandbox] isolated-vm Isolate n\xE3o encontrado");
2434
+ return null;
2435
+ }
2436
+ return ivmModule;
2437
+ } catch (e) {
2438
+ console.warn("[Sandbox] isolated-vm n\xE3o dispon\xEDvel, usando fallback inseguro");
2439
+ return null;
2440
+ }
2441
+ }
2442
+ var DEFAULT_OPTIONS = {
2443
+ timeout: 1e4,
2444
+ memoryLimit: 128,
2445
+ allowFetch: true
2446
+ };
2447
+ async function executeSandboxed(code, sandboxContext, options = {}) {
2448
+ const opts = { ...DEFAULT_OPTIONS, ...options };
2449
+ const ivm = await getIVM();
2450
+ if (!ivm) {
2451
+ return executeFallback(code, sandboxContext);
2452
+ }
2453
+ let isolate = null;
2454
+ let context = null;
2455
+ try {
2456
+ isolate = new ivm.Isolate({ memoryLimit: opts.memoryLimit });
2457
+ context = await isolate.createContext();
2458
+ const jail = context.global;
2459
+ await jail.set("globalThis", jail.derefInto());
2460
+ await injectContextVariables(jail, sandboxContext, ivm);
2461
+ await jail.set("__logRef", new ivm.Reference(function(...args) {
2462
+ console.log("[Sandbox]", ...args);
2463
+ }));
2464
+ if (opts.allowFetch) {
2465
+ await jail.set("__fetchRef", new ivm.Reference(async function(url, optionsJson) {
2466
+ try {
2467
+ const options2 = optionsJson ? JSON.parse(optionsJson) : {};
2468
+ const res = await fetch(url, {
2469
+ method: options2?.method || "GET",
2470
+ headers: options2?.headers,
2471
+ body: options2?.body
2472
+ });
2473
+ const contentType = res.headers.get("content-type") || "";
2474
+ let data;
2475
+ if (contentType.includes("application/json")) {
2476
+ data = await res.json();
2477
+ } else {
2478
+ data = await res.text();
2479
+ }
2480
+ return JSON.stringify({
2481
+ ok: res.ok,
2482
+ status: res.status,
2483
+ statusText: res.statusText,
2484
+ data
2485
+ });
2486
+ } catch (error) {
2487
+ return JSON.stringify({
2488
+ ok: false,
2489
+ status: 0,
2490
+ statusText: "Network Error",
2491
+ error: error.message
2492
+ });
2493
+ }
2494
+ }));
2495
+ }
2496
+ const bootstrapCode = `
2497
+ // Console
2498
+ const console = {
2499
+ log: (...args) => __logRef.applySync(undefined, args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a))),
2500
+ warn: (...args) => __logRef.applySync(undefined, ['[WARN]', ...args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a))]),
2501
+ error: (...args) => __logRef.applySync(undefined, ['[ERROR]', ...args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a))]),
2502
+ info: (...args) => __logRef.applySync(undefined, args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a))),
2503
+ };
2504
+ globalThis.console = console;
2505
+
2506
+ ${opts.allowFetch ? `
2507
+ // Fetch
2508
+ globalThis.fetch = async (url, options = {}) => {
2509
+ const optionsJson = JSON.stringify(options);
2510
+ const resultJson = await __fetchRef.apply(undefined, [url, optionsJson], { result: { promise: true } });
2511
+ const result = JSON.parse(resultJson);
2512
+
2513
+ if (!result.ok && result.error) {
2514
+ throw new Error(result.error);
2515
+ }
2516
+
2517
+ return {
2518
+ ok: result.ok,
2519
+ status: result.status,
2520
+ statusText: result.statusText,
2521
+ json: async () => result.data,
2522
+ text: async () => typeof result.data === 'string' ? result.data : JSON.stringify(result.data),
2523
+ };
2524
+ };
2525
+ ` : ""}
2526
+
2527
+ // Base64
2528
+ globalThis.atob = (str) => {
2529
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
2530
+ let output = '';
2531
+ str = String(str).replace(/=+$/, '');
2532
+ for (let i = 0; i < str.length; i += 4) {
2533
+ const enc1 = chars.indexOf(str.charAt(i));
2534
+ const enc2 = chars.indexOf(str.charAt(i + 1));
2535
+ const enc3 = chars.indexOf(str.charAt(i + 2));
2536
+ const enc4 = chars.indexOf(str.charAt(i + 3));
2537
+ const chr1 = (enc1 << 2) | (enc2 >> 4);
2538
+ const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
2539
+ const chr3 = ((enc3 & 3) << 6) | enc4;
2540
+ output += String.fromCharCode(chr1);
2541
+ if (enc3 !== 64) output += String.fromCharCode(chr2);
2542
+ if (enc4 !== 64) output += String.fromCharCode(chr3);
2543
+ }
2544
+ return output;
2545
+ };
2546
+
2547
+ globalThis.btoa = (str) => {
2548
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
2549
+ str = String(str);
2550
+ let output = '';
2551
+ for (let i = 0; i < str.length; i += 3) {
2552
+ const chr1 = str.charCodeAt(i);
2553
+ const chr2 = str.charCodeAt(i + 1);
2554
+ const chr3 = str.charCodeAt(i + 2);
2555
+ const enc1 = chr1 >> 2;
2556
+ const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2557
+ const enc3 = isNaN(chr2) ? 64 : ((chr2 & 15) << 2) | (chr3 >> 6);
2558
+ const enc4 = isNaN(chr3) ? 64 : chr3 & 63;
2559
+ output += chars.charAt(enc1) + chars.charAt(enc2) + chars.charAt(enc3) + chars.charAt(enc4);
2560
+ }
2561
+ return output;
2562
+ };
2563
+ `;
2564
+ const bootstrapScript = await isolate.compileScript(bootstrapCode);
2565
+ await bootstrapScript.run(context);
2566
+ const wrappedCode = wrapUserCode(code);
2567
+ const finalCode = `
2568
+ (async () => {
2569
+ const __userResult = await (${wrappedCode});
2570
+ // Serializa o resultado para poder transferir
2571
+ if (__userResult === undefined) return '__UNDEFINED__';
2572
+ if (__userResult === null) return 'null';
2573
+ if (typeof __userResult === 'object') {
2574
+ return JSON.stringify({ __type: 'object', value: __userResult });
2575
+ }
2576
+ return JSON.stringify({ __type: typeof __userResult, value: __userResult });
2577
+ })()
2578
+ `;
2579
+ const script = await isolate.compileScript(finalCode);
2580
+ const rawResult = await script.run(context, {
2581
+ timeout: opts.timeout,
2582
+ promise: true
2583
+ });
2584
+ if (rawResult === "__UNDEFINED__") return void 0;
2585
+ if (rawResult === "null") return null;
2586
+ try {
2587
+ const parsed = JSON.parse(rawResult);
2588
+ return parsed.value;
2589
+ } catch {
2590
+ return rawResult;
2591
+ }
2592
+ } catch (error) {
2593
+ if (error.message?.includes("Script execution timed out")) {
2594
+ throw new Error("Tempo de execu\xE7\xE3o excedido (timeout)");
2595
+ }
2596
+ if (error.message?.includes("Isolate was disposed")) {
2597
+ throw new Error("Execu\xE7\xE3o cancelada: limite de mem\xF3ria excedido");
2598
+ }
2599
+ throw new Error(`Erro na execu\xE7\xE3o do c\xF3digo: ${error.message || "Erro desconhecido"}`);
2600
+ } finally {
2601
+ if (context) {
2602
+ try {
2603
+ context.release();
2604
+ } catch {
2605
+ }
2606
+ }
2607
+ if (isolate) {
2608
+ try {
2609
+ isolate.dispose();
2610
+ } catch {
2611
+ }
2612
+ }
2613
+ }
2614
+ }
2615
+ async function injectContextVariables(jail, ctx, ivm) {
2616
+ const safeClone = (value) => {
2617
+ if (value === void 0 || value === null) {
2618
+ return value;
2619
+ }
2620
+ try {
2621
+ if (typeof value !== "object") {
2622
+ return value;
2623
+ }
2624
+ return new ivm.ExternalCopy(value).copyInto();
2625
+ } catch {
2626
+ try {
2627
+ return JSON.parse(JSON.stringify(value));
2628
+ } catch {
2629
+ return void 0;
2630
+ }
2631
+ }
2632
+ };
2633
+ await jail.set("input", safeClone(ctx.input));
2634
+ await jail.set("context", safeClone(ctx.context ?? {}));
2635
+ await jail.set("$inputs", safeClone(ctx.$inputs ?? {}));
2636
+ await jail.set("$vars", safeClone(ctx.$vars ?? {}));
2637
+ await jail.set("$field", safeClone(ctx.$field ?? {}));
2638
+ await jail.set("request", safeClone(ctx.request));
2639
+ await jail.set("params", safeClone(ctx.params));
2640
+ await jail.set("__placeholders", safeClone(ctx.__placeholders ?? {}));
2641
+ }
2642
+ function wrapUserCode(code) {
2643
+ const trimmed = code.trim();
2644
+ if (trimmed.startsWith("return ")) {
2645
+ return `(async function() { ${code} })()`;
2646
+ }
2647
+ const isSimpleExpression = !trimmed.includes("{") && !trimmed.includes("if ") && !trimmed.includes("for ") && !trimmed.includes("while ") && !trimmed.includes("function ") && !trimmed.includes("const ") && !trimmed.includes("let ") && !trimmed.includes("var ");
2648
+ if (isSimpleExpression) {
2649
+ return `(async function() { return ${code} })()`;
2650
+ }
2651
+ return `(async function() { ${code} })()`;
2652
+ }
2653
+ function executeFallback(code, ctx) {
2654
+ console.warn("[Sandbox] Usando execu\xE7\xE3o INSEGURA (new Function). Instale isolated-vm para seguran\xE7a.");
2655
+ const customFunction = new Function(
2656
+ "input",
2657
+ "context",
2658
+ "request",
2659
+ "params",
2660
+ "$inputs",
2661
+ "$vars",
2662
+ "$field",
2663
+ "__placeholders",
2664
+ code
2665
+ );
2666
+ return customFunction(
2667
+ ctx.input,
2668
+ ctx.context,
2669
+ ctx.request,
2670
+ ctx.params,
2671
+ ctx.$inputs,
2672
+ ctx.$vars,
2673
+ ctx.$field,
2674
+ ctx.__placeholders
2675
+ );
2676
+ }
2677
+ function looksLikeCode(code) {
2678
+ if (typeof code !== "string") return false;
2679
+ const c = code.trim();
2680
+ if (c.length < 3) return false;
2681
+ return /(return\s+|=>|function\s*\(|;|\n|\{|\})/.test(c);
2682
+ }
2683
+
2314
2684
  // src/nodes/processors/custom-code.ts
2315
- var NodeFunction = (params) => {
2685
+ var NodeFunction = async (params) => {
2316
2686
  let input = params?.inputValue ?? params?.input;
2317
2687
  const context = params && params.fieldValues ? params.fieldValues : params || {};
2318
2688
  let customCode = context?.customCode ?? params?.customCode;
@@ -2325,12 +2695,6 @@ var NodeFunction = (params) => {
2325
2695
  else if (firstInputField.value !== void 0) input = firstInputField.value;
2326
2696
  }
2327
2697
  }
2328
- const looksLikeCode = (code) => {
2329
- if (typeof code !== "string") return false;
2330
- const c = code.trim();
2331
- if (c.length < 3) return false;
2332
- return /(return\s+|=>|function\s*\(|;|\n|\{|\})/.test(c);
2333
- };
2334
2698
  if (typeof customCode === "string" && !looksLikeCode(customCode)) {
2335
2699
  if (input === void 0) input = customCode;
2336
2700
  customCode = "";
@@ -2338,18 +2702,25 @@ var NodeFunction = (params) => {
2338
2702
  if (!customCode || typeof customCode === "string" && customCode.trim() === "") {
2339
2703
  return input;
2340
2704
  }
2341
- try {
2342
- const $inputs = params?.results || {};
2343
- const $vars = context && context.variables || params?.variables || void 0;
2344
- const __placeholders = params?.__codePlaceholders ?? context?.__codePlaceholders;
2345
- const customFunction = new Function("input", "context", "request", "params", "$inputs", "$vars", "__placeholders", customCode);
2346
- const result = customFunction(input, context, params?.request, params, $inputs, $vars, __placeholders);
2347
- return result;
2348
- } catch (error) {
2349
- throw new Error(`Erro ao executar c\xF3digo customizado: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
2350
- }
2705
+ const sandboxContext = {
2706
+ input,
2707
+ context,
2708
+ $inputs: params?.results || {},
2709
+ $vars: context && context.variables || params?.variables || void 0,
2710
+ request: params?.request,
2711
+ params,
2712
+ __placeholders: params?.__codePlaceholders ?? context?.__codePlaceholders
2713
+ };
2714
+ const result = await executeSandboxed(customCode, sandboxContext, {
2715
+ timeout: 3e4,
2716
+ // 30 segundos
2717
+ memoryLimit: 128,
2718
+ // 128MB
2719
+ allowFetch: true
2720
+ });
2721
+ return result;
2351
2722
  };
2352
- var CustomNodeFunction = (params) => {
2723
+ var CustomNodeFunction = async (params) => {
2353
2724
  const context = params && params.fieldValues ? params.fieldValues : params || {};
2354
2725
  const customCode = context?.customCode;
2355
2726
  if (!customCode || typeof customCode === "string" && customCode.trim() === "") {
@@ -2372,29 +2743,36 @@ var CustomNodeFunction = (params) => {
2372
2743
  }
2373
2744
  $field[fieldId] = value;
2374
2745
  });
2375
- try {
2376
- const $inputs = params?.results || {};
2377
- const $vars = context && context.variables || params?.variables || void 0;
2378
- const customFunction = new Function("$field", "context", "request", "params", "$inputs", "$vars", customCode);
2379
- const result = customFunction($field, context, params?.request, params, $inputs, $vars);
2380
- return result;
2381
- } catch (error) {
2382
- throw new Error(`Erro ao executar CustomNode: ${error instanceof Error ? error.message : "Erro desconhecido"}`);
2383
- }
2746
+ const sandboxContext = {
2747
+ $field,
2748
+ context,
2749
+ $inputs: params?.results || {},
2750
+ $vars: context && context.variables || params?.variables || void 0,
2751
+ request: params?.request,
2752
+ params
2753
+ };
2754
+ const result = await executeSandboxed(customCode, sandboxContext, {
2755
+ timeout: 3e4,
2756
+ // 30 segundos
2757
+ memoryLimit: 128,
2758
+ // 128MB
2759
+ allowFetch: true
2760
+ });
2761
+ return result;
2384
2762
  };
2385
2763
  var CustomCodeNode = {
2386
2764
  label: "Custom Code",
2387
2765
  type: "CustomCodeNode",
2388
2766
  category: "step",
2389
2767
  icon: "\u{1F4BB}",
2390
- description: "Node para executar c\xF3digo JavaScript customizado",
2768
+ description: "Node para executar c\xF3digo JavaScript customizado (sandbox segura)",
2391
2769
  fields: [
2392
2770
  {
2393
2771
  id: "customCode",
2394
2772
  label: "C\xF3digo Customizado",
2395
2773
  type: "code",
2396
2774
  required: false,
2397
- placeholder: '// Seu c\xF3digo JavaScript aqui\n// Use "input" para acessar o valor de entrada\n// Use "context" para acessar os dados do n\xF3\nreturn input;'
2775
+ placeholder: '// Seu c\xF3digo JavaScript aqui\n// Use "input" para acessar o valor de entrada\n// Use "context" para acessar os dados do n\xF3\n// fetch(), JSON, Math, Date est\xE3o dispon\xEDveis\nreturn input;'
2398
2776
  },
2399
2777
  {
2400
2778
  id: "input",
@@ -2856,6 +3234,335 @@ var ModelGuardrailNodeFunction = async (inputs) => {
2856
3234
  };
2857
3235
  };
2858
3236
 
3237
+ // src/nodes/chat/getOrCreateThread/data.ts
3238
+ import { z as z17 } from "zod";
3239
+ var GetOrCreateThreadSchema = z17.object({
3240
+ source: z17.string().describe("Channel source: whatsapp, telegram, web, etc."),
3241
+ identifier: z17.string().describe("User identifier: phone number, chat_id, userId"),
3242
+ timeoutMinutes: z17.number().optional().describe("Inactivity timeout in minutes (default: 1440 = 24h)")
3243
+ });
3244
+ var GetOrCreateThreadNode = {
3245
+ label: "Get or Create Thread",
3246
+ type: "GetOrCreateThreadNode",
3247
+ category: "chat",
3248
+ description: "Generates or retrieves a threadId based on identifier + timeout. Ensures tenant isolation.",
3249
+ icon: "\u{1F9F5}",
3250
+ group: "Chat",
3251
+ tags: {
3252
+ execution: "async",
3253
+ group: "Chat"
3254
+ },
3255
+ fields: [
3256
+ {
3257
+ id: "source",
3258
+ label: "Source",
3259
+ type: "string",
3260
+ required: true,
3261
+ defaultValue: "whatsapp",
3262
+ placeholder: "whatsapp, telegram, web",
3263
+ handle: {
3264
+ type: "input",
3265
+ label: "Source",
3266
+ name: "source",
3267
+ fieldType: "string"
3268
+ }
3269
+ },
3270
+ {
3271
+ id: "identifier",
3272
+ label: "Identifier",
3273
+ type: "string",
3274
+ required: true,
3275
+ placeholder: "+5511999999999",
3276
+ handle: {
3277
+ type: "input",
3278
+ label: "Identifier",
3279
+ name: "identifier",
3280
+ fieldType: "string"
3281
+ }
3282
+ },
3283
+ {
3284
+ id: "timeoutMinutes",
3285
+ label: "Timeout (minutes)",
3286
+ type: "number",
3287
+ required: false,
3288
+ defaultValue: "1440",
3289
+ placeholder: "1440 (24 hours)"
3290
+ },
3291
+ {
3292
+ id: "threadId",
3293
+ label: "Thread ID",
3294
+ type: "string",
3295
+ required: true,
3296
+ typeable: false,
3297
+ handle: {
3298
+ type: "output",
3299
+ label: "Thread ID",
3300
+ name: "threadId",
3301
+ fieldType: "string"
3302
+ }
3303
+ },
3304
+ {
3305
+ id: "isNew",
3306
+ label: "Is New Thread",
3307
+ type: "boolean",
3308
+ required: true,
3309
+ typeable: false,
3310
+ handle: {
3311
+ type: "output",
3312
+ label: "Is New",
3313
+ name: "isNew",
3314
+ fieldType: "boolean"
3315
+ }
3316
+ }
3317
+ ]
3318
+ };
3319
+
3320
+ // src/nodes/chat/getOrCreateThread/function.ts
3321
+ var GetOrCreateThreadNodeFunction = async (inputs) => {
3322
+ const fieldValues = inputs.fieldValues || {};
3323
+ const context = inputs.context || {};
3324
+ const source = fieldValues.source;
3325
+ const identifier = fieldValues.identifier;
3326
+ const timeoutMinutes = Number(fieldValues.timeoutMinutes) || 1440;
3327
+ const companyId = context?.companyId;
3328
+ if (!companyId) {
3329
+ throw new Error("GetOrCreateThread requires companyId in context for tenant isolation");
3330
+ }
3331
+ if (!source || !identifier) {
3332
+ throw new Error("GetOrCreateThread requires source and identifier");
3333
+ }
3334
+ const timeoutMs = timeoutMinutes * 60 * 1e3;
3335
+ const thresholdDate = new Date(Date.now() - timeoutMs);
3336
+ const db = context?.db;
3337
+ if (!db) {
3338
+ throw new Error("GetOrCreateThread requires database connection in context");
3339
+ }
3340
+ try {
3341
+ const existingSessions = await db.query(`
3342
+ SELECT id, session_id, updated_at
3343
+ FROM chat_sessions
3344
+ WHERE company_id = $1
3345
+ AND metadata->>'source' = $2
3346
+ AND metadata->>'identifier' = $3
3347
+ AND updated_at > $4
3348
+ ORDER BY updated_at DESC
3349
+ LIMIT 1
3350
+ `, [companyId, source, identifier, thresholdDate]);
3351
+ if (existingSessions.rows && existingSessions.rows.length > 0) {
3352
+ const session = existingSessions.rows[0];
3353
+ await db.query(`
3354
+ UPDATE chat_sessions
3355
+ SET updated_at = NOW()
3356
+ WHERE id = $1 AND company_id = $2
3357
+ `, [session.id, companyId]);
3358
+ return {
3359
+ threadId: session.session_id,
3360
+ isNew: false
3361
+ };
3362
+ }
3363
+ const newSessionId = crypto.randomUUID();
3364
+ const workflowId = context?.workflowId;
3365
+ await db.query(`
3366
+ INSERT INTO chat_sessions (workflow_id, company_id, chat_id, session_id, metadata)
3367
+ VALUES ($1, $2, $3, $4, $5)
3368
+ `, [
3369
+ workflowId || companyId,
3370
+ // fallback to companyId if no workflowId
3371
+ companyId,
3372
+ `${source}:${identifier}`,
3373
+ // chatId as composite key
3374
+ newSessionId,
3375
+ JSON.stringify({ source, identifier })
3376
+ ]);
3377
+ return {
3378
+ threadId: newSessionId,
3379
+ isNew: true
3380
+ };
3381
+ } catch (error) {
3382
+ console.error("[GetOrCreateThread] Error:", error);
3383
+ throw error;
3384
+ }
3385
+ };
3386
+
3387
+ // src/nodes/chat/chatLog/data.ts
3388
+ import { z as z18 } from "zod";
3389
+ var ChatLogSchema = z18.object({
3390
+ threadId: z18.string().describe("Thread ID from GetOrCreateThread"),
3391
+ direction: z18.enum(["inbound", "outbound"]).describe("Message direction"),
3392
+ content: z18.string().describe("Message content"),
3393
+ source: z18.string().optional().describe("Channel source"),
3394
+ identifier: z18.string().optional().describe("User identifier"),
3395
+ metadata: z18.record(z18.string(), z18.any()).optional().describe("Additional metadata")
3396
+ });
3397
+ var ChatLogNode = {
3398
+ label: "Chat Log",
3399
+ type: "ChatLogNode",
3400
+ category: "chat",
3401
+ description: "Saves a message to the chat history. Supports inbound (user) and outbound (assistant) messages.",
3402
+ icon: "\u{1F4AC}",
3403
+ group: "Chat",
3404
+ tags: {
3405
+ execution: "async",
3406
+ group: "Chat"
3407
+ },
3408
+ fields: [
3409
+ {
3410
+ id: "threadId",
3411
+ label: "Thread ID",
3412
+ type: "string",
3413
+ required: true,
3414
+ placeholder: "From GetOrCreateThread node",
3415
+ handle: {
3416
+ type: "input",
3417
+ label: "Thread ID",
3418
+ name: "threadId",
3419
+ fieldType: "string"
3420
+ }
3421
+ },
3422
+ {
3423
+ id: "direction",
3424
+ label: "Direction",
3425
+ type: "select",
3426
+ required: true,
3427
+ defaultValue: "inbound",
3428
+ options: [
3429
+ { label: "Inbound (User \u2192 System)", value: "inbound" },
3430
+ { label: "Outbound (System \u2192 User)", value: "outbound" }
3431
+ ]
3432
+ },
3433
+ {
3434
+ id: "content",
3435
+ label: "Content",
3436
+ type: "string",
3437
+ required: true,
3438
+ placeholder: "Message content",
3439
+ handle: {
3440
+ type: "input",
3441
+ label: "Content",
3442
+ name: "content",
3443
+ fieldType: "string"
3444
+ }
3445
+ },
3446
+ {
3447
+ id: "source",
3448
+ label: "Source",
3449
+ type: "string",
3450
+ required: false,
3451
+ defaultValue: "whatsapp",
3452
+ placeholder: "whatsapp, telegram, web",
3453
+ handle: {
3454
+ type: "input",
3455
+ label: "Source",
3456
+ name: "source",
3457
+ fieldType: "string"
3458
+ }
3459
+ },
3460
+ {
3461
+ id: "identifier",
3462
+ label: "Identifier",
3463
+ type: "string",
3464
+ required: false,
3465
+ placeholder: "+5511999999999",
3466
+ handle: {
3467
+ type: "input",
3468
+ label: "Identifier",
3469
+ name: "identifier",
3470
+ fieldType: "string"
3471
+ }
3472
+ },
3473
+ {
3474
+ id: "messageId",
3475
+ label: "Message ID",
3476
+ type: "string",
3477
+ required: true,
3478
+ typeable: false,
3479
+ handle: {
3480
+ type: "output",
3481
+ label: "Message ID",
3482
+ name: "messageId",
3483
+ fieldType: "string"
3484
+ }
3485
+ },
3486
+ {
3487
+ id: "success",
3488
+ label: "Success",
3489
+ type: "boolean",
3490
+ required: true,
3491
+ typeable: false,
3492
+ handle: {
3493
+ type: "output",
3494
+ label: "Success",
3495
+ name: "success",
3496
+ fieldType: "boolean"
3497
+ }
3498
+ }
3499
+ ]
3500
+ };
3501
+
3502
+ // src/nodes/chat/chatLog/function.ts
3503
+ var ChatLogNodeFunction = async (inputs) => {
3504
+ const fieldValues = inputs.fieldValues || {};
3505
+ const context = inputs.context || {};
3506
+ const threadId = fieldValues.threadId;
3507
+ const direction = fieldValues.direction;
3508
+ const content = fieldValues.content;
3509
+ const source = fieldValues.source;
3510
+ const identifier = fieldValues.identifier;
3511
+ const companyId = context?.companyId;
3512
+ if (!companyId) {
3513
+ throw new Error("ChatLog requires companyId in context for tenant isolation");
3514
+ }
3515
+ if (!threadId || !content) {
3516
+ throw new Error("ChatLog requires threadId and content");
3517
+ }
3518
+ const role = direction === "inbound" ? "user" : "assistant";
3519
+ const db = context?.db;
3520
+ if (!db) {
3521
+ throw new Error("ChatLog requires database connection in context");
3522
+ }
3523
+ try {
3524
+ const sessionResult = await db.query(`
3525
+ SELECT id FROM chat_sessions
3526
+ WHERE session_id = $1 AND company_id = $2
3527
+ LIMIT 1
3528
+ `, [threadId, companyId]);
3529
+ if (!sessionResult.rows || sessionResult.rows.length === 0) {
3530
+ throw new Error(`Session not found for threadId: ${threadId}`);
3531
+ }
3532
+ const sessionId = sessionResult.rows[0].id;
3533
+ const messageId = crypto.randomUUID();
3534
+ await db.query(`
3535
+ INSERT INTO chat_messages (id, session_id, role, content, metadata)
3536
+ VALUES ($1, $2, $3, $4, $5)
3537
+ `, [
3538
+ messageId,
3539
+ sessionId,
3540
+ role,
3541
+ content,
3542
+ JSON.stringify({
3543
+ direction,
3544
+ source: source || null,
3545
+ identifier: identifier || null
3546
+ })
3547
+ ]);
3548
+ await db.query(`
3549
+ UPDATE chat_sessions
3550
+ SET updated_at = NOW()
3551
+ WHERE id = $1 AND company_id = $2
3552
+ `, [sessionId, companyId]);
3553
+ return {
3554
+ messageId,
3555
+ success: true
3556
+ };
3557
+ } catch (error) {
3558
+ console.error("[ChatLog] Error:", error);
3559
+ return {
3560
+ messageId: null,
3561
+ success: false
3562
+ };
3563
+ }
3564
+ };
3565
+
2859
3566
  // src/nodes/consts/nodes.ts
2860
3567
  var nodes = [
2861
3568
  ChatInputNode,
@@ -2882,7 +3589,9 @@ var nodes = [
2882
3589
  PromptGuardrailNode,
2883
3590
  RuleGuardrailNode,
2884
3591
  FunctionGuardrailNode,
2885
- ModelGuardrailNode
3592
+ ModelGuardrailNode,
3593
+ GetOrCreateThreadNode,
3594
+ ChatLogNode
2886
3595
  ];
2887
3596
  var nodes_default = nodes;
2888
3597
 
@@ -3004,7 +3713,9 @@ var nodeFunctions = {
3004
3713
  PromptGuardrailNode: PromptGuardrailNodeFunction,
3005
3714
  RuleGuardrailNode: RuleGuardrailNodeFunction,
3006
3715
  FunctionGuardrailNode: FunctionGuardrailNodeFunction,
3007
- ModelGuardrailNode: ModelGuardrailNodeFunction
3716
+ ModelGuardrailNode: ModelGuardrailNodeFunction,
3717
+ GetOrCreateThreadNode: GetOrCreateThreadNodeFunction,
3718
+ ChatLogNode: ChatLogNodeFunction
3008
3719
  };
3009
3720
  var node_functions_default = nodeFunctions;
3010
3721
 
@@ -3118,12 +3829,12 @@ var HttpPutInputNodeFunction = (params) => {
3118
3829
  };
3119
3830
 
3120
3831
  // src/nodes/inputs/http/put/schema.ts
3121
- import { z as z17 } from "zod";
3122
- var HttpPutInputNodeSchema = z17.object({
3832
+ import { z as z19 } from "zod";
3833
+ var HttpPutInputNodeSchema = z19.object({
3123
3834
  route: RouteSchema,
3124
- queryParams: z17.array(QueryParamSchema).optional().describe("Query parameters configuration"),
3125
- headers: z17.array(HeaderSchema).optional().describe("Headers configuration"),
3126
- body: z17.array(BodyFieldSchema).optional().describe("Body fields configuration")
3835
+ queryParams: z19.array(QueryParamSchema).optional().describe("Query parameters configuration"),
3836
+ headers: z19.array(HeaderSchema).optional().describe("Headers configuration"),
3837
+ body: z19.array(BodyFieldSchema).optional().describe("Body fields configuration")
3127
3838
  });
3128
3839
 
3129
3840
  // src/nodes/inputs/http/delete/data.ts
@@ -3215,11 +3926,11 @@ var HttpDeleteInputNodeFunction = async (params) => {
3215
3926
  };
3216
3927
 
3217
3928
  // src/nodes/inputs/http/delete/schema.ts
3218
- import { z as z18 } from "zod";
3219
- var HttpDeleteInputNodeSchema = z18.object({
3929
+ import { z as z20 } from "zod";
3930
+ var HttpDeleteInputNodeSchema = z20.object({
3220
3931
  route: RouteSchema,
3221
- queryParams: z18.array(QueryParamSchema).optional().describe("Query parameters configuration"),
3222
- headers: z18.array(HeaderSchema).optional().describe("Headers configuration")
3932
+ queryParams: z20.array(QueryParamSchema).optional().describe("Query parameters configuration"),
3933
+ headers: z20.array(HeaderSchema).optional().describe("Headers configuration")
3223
3934
  });
3224
3935
 
3225
3936
  // src/nodes/inputs/http/patch/data.ts
@@ -3332,12 +4043,12 @@ var HttpPatchInputNodeFunction = (params) => {
3332
4043
  };
3333
4044
 
3334
4045
  // src/nodes/inputs/http/patch/schema.ts
3335
- import { z as z19 } from "zod";
3336
- var HttpPatchInputNodeSchema = z19.object({
4046
+ import { z as z21 } from "zod";
4047
+ var HttpPatchInputNodeSchema = z21.object({
3337
4048
  route: RouteSchema,
3338
- queryParams: z19.array(QueryParamSchema).optional().describe("Query parameters configuration"),
3339
- headers: z19.array(HeaderSchema).optional().describe("Headers configuration"),
3340
- body: z19.array(BodyFieldSchema).optional().describe("Body fields configuration")
4049
+ queryParams: z21.array(QueryParamSchema).optional().describe("Query parameters configuration"),
4050
+ headers: z21.array(HeaderSchema).optional().describe("Headers configuration"),
4051
+ body: z21.array(BodyFieldSchema).optional().describe("Body fields configuration")
3341
4052
  });
3342
4053
 
3343
4054
  // src/nodes/inputs/http/utils.ts
@@ -3396,7 +4107,13 @@ export {
3396
4107
  AiToolNodeFunction,
3397
4108
  AiToolNodeSchema,
3398
4109
  BodyFieldSchema,
4110
+ ChatLogNode,
4111
+ ChatLogNodeFunction,
4112
+ ChatLogSchema,
3399
4113
  CustomToolSchema,
4114
+ GetOrCreateThreadNode,
4115
+ GetOrCreateThreadNodeFunction,
4116
+ GetOrCreateThreadSchema,
3400
4117
  HTTP_METHODS,
3401
4118
  HTTP_NODE_TYPES,
3402
4119
  HeaderSchema,