@path58/p58-n8n 0.2.16 → 0.2.17

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.
@@ -22145,6 +22145,7 @@ __export(n8n_api_adapter_exports, {
22145
22145
  deleteCredential: () => deleteCredential,
22146
22146
  deleteExecution: () => deleteExecution,
22147
22147
  deleteWorkflow: () => deleteWorkflow,
22148
+ executeWorkflowViaRest: () => executeWorkflowViaRest,
22148
22149
  extractNodeDebugData: () => extractNodeDebugData,
22149
22150
  getCredentialWithData: () => getCredentialWithData,
22150
22151
  getExecution: () => getExecution,
@@ -22332,6 +22333,34 @@ async function activateWorkflow(config2, workflowId) {
22332
22333
  throw buildN8nError(error);
22333
22334
  }
22334
22335
  }
22336
+ async function executeWorkflowViaRest(config2, workflowId, workflowData) {
22337
+ const email = process.env.N8N_USER_EMAIL;
22338
+ const password = process.env.N8N_USER_PASSWORD;
22339
+ if (!email || !password) {
22340
+ throw new Error("N8N_USER_EMAIL and N8N_USER_PASSWORD are required for REST workflow execution");
22341
+ }
22342
+ const restBaseUrl = deriveInstanceBaseUrl(config2.baseUrl);
22343
+ const cookie = await getRestSessionCookieWith(restBaseUrl, email, password);
22344
+ const cfHeaders = {};
22345
+ if (process.env.CF_ACCESS_CLIENT_ID && process.env.CF_ACCESS_CLIENT_SECRET) {
22346
+ cfHeaders["CF-Access-Client-Id"] = process.env.CF_ACCESS_CLIENT_ID;
22347
+ cfHeaders["CF-Access-Client-Secret"] = process.env.CF_ACCESS_CLIENT_SECRET;
22348
+ }
22349
+ const requestBody = workflowData && typeof workflowData === "object" && !Array.isArray(workflowData) ? { workflowData: { ...workflowData, id: workflowId } } : workflowData ? { workflowData } : {};
22350
+ try {
22351
+ const res = await axios_default.post(`${restBaseUrl}/rest/workflows/${workflowId}/run`, requestBody, {
22352
+ timeout: config2.timeoutMs ?? 3e4,
22353
+ headers: {
22354
+ Cookie: cookie,
22355
+ "Content-Type": "application/json",
22356
+ ...cfHeaders
22357
+ }
22358
+ });
22359
+ return res.data?.data ?? {};
22360
+ } catch (error) {
22361
+ throw buildN8nError(error);
22362
+ }
22363
+ }
22335
22364
  async function deactivateWorkflow(config2, workflowId) {
22336
22365
  const client = createN8nHttpClient(config2);
22337
22366
  try {
@@ -41626,6 +41655,11 @@ async function buildNodeInfoResponse(nodeType, includeSections) {
41626
41655
  response.best_practices = node.best_practices ?? null;
41627
41656
  response.common_errors = node.common_errors ?? null;
41628
41657
  response.node_role = node.node_role ?? null;
41658
+ } else {
41659
+ const configHints = node.best_practices?.configuration;
41660
+ if (Array.isArray(configHints) && configHints.length > 0) {
41661
+ response.best_practices = { configuration: configHints };
41662
+ }
41629
41663
  }
41630
41664
  if (nodeType === "n8n-nodes-base.code") {
41631
41665
  response.coding_reference = CODE_NODE_REFERENCE;
@@ -41740,14 +41774,15 @@ Returns:
41740
41774
  - properties: Node-level configuration properties (optional)
41741
41775
  - config_examples: Real-world configuration examples from validated workflows (optional)
41742
41776
  - version_info: Available type versions for this node (optional)
41743
- - enrichment: best_practices (structured JSON), common_errors (structured JSON), node_role (string) \u2014 opt-in only, not included by default
41777
+ - enrichment: best_practices (structured JSON), common_errors (structured JSON), node_role (string) \u2014 best_practices.configuration is auto-included when non-empty; full enrichment is opt-in
41744
41778
  - agent_card: summary, primary_use_case, node_class, discovery_paths, search_triggers \u2014 opt-in only
41745
41779
  - ai_metadata: AI/LangChain node specialization \u2014 ai_type, supported_models, vendor, input/output types, chain_patterns, capabilities \u2014 opt-in only, returns null for non-AI nodes
41746
41780
  - docs: documentation facts with source references (fact_type, fact text, confidence, page_title, source_url) \u2014 opt-in only, returns empty array for nodes without doc coverage
41747
41781
 
41748
41782
  Use the 'include' parameter to request only specific sections for efficiency.
41749
41783
  If 'include' is omitted, default sections are returned (operations, credentials, properties, config_examples, version_info).
41750
- The 'enrichment', 'agent_card', 'ai_metadata', and 'docs' sections are opt-in \u2014 request them explicitly when you need node intelligence context.
41784
+ best_practices.configuration is automatically included when non-empty \u2014 no opt-in needed for configuration hints.
41785
+ The 'enrichment' (full), 'agent_card', 'ai_metadata', and 'docs' sections are opt-in \u2014 request them explicitly when you need node intelligence context.
41751
41786
 
41752
41787
  Examples:
41753
41788
  - { "node_type": "n8n-nodes-base.httpRequest" } \u2192 Default sections (no enrichment)
@@ -41967,13 +42002,12 @@ Returns workflow_id and URL on success.`,
41967
42002
 
41968
42003
  // dist/mcp/tools/handlers/execute-workflow.js
41969
42004
  init_n8n_api_adapter();
41970
- var import_node_crypto2 = require("node:crypto");
42005
+ var import_node_crypto = require("node:crypto");
41971
42006
  init_timeout();
41972
42007
  var import_logging34 = __toESM(require_logging(), 1);
41973
42008
 
41974
42009
  // dist/mcp/tools/handlers/test-workflow.js
41975
42010
  init_n8n_api_adapter();
41976
- var import_node_crypto = require("node:crypto");
41977
42011
  init_timeout();
41978
42012
  var import_logging33 = __toESM(require_logging(), 1);
41979
42013
  init_adapters2();
@@ -41999,6 +42033,7 @@ var CREDENTIAL_HINTS = {
41999
42033
  var WEBHOOK_NOT_READY_HINT = "Webhook endpoint initialization delay \u2014 the workflow is deployed. Call test_workflow directly to retry the test.";
42000
42034
  var WEBHOOK_CONFLICT_HINT = "Webhook URL conflict \u2014 another workflow is using the same webhook path. Call list_workflows to find the conflicting workflow, then either delete_workflow the old one or use partial_update_workflow to change this webhook path. Do NOT use Bash.";
42001
42035
  var SCHEMA_FORMAT_HINT = "Input format error in operations payload. Check that array fields are arrays and string fields are strings. Review partial_update_workflow schema before retrying.";
42036
+ var MISSING_CREDENTIAL_TYPE_HINT = "Required credential type not available in n8n. Call list_credentials to check what credential types exist. If the required type is missing, call get_node_info on the failing node to check alternative auth modes (e.g. Service Account instead of OAuth2). If no compatible credential type exists, call build_workflow to rebuild using a webhook trigger or a node that uses an available credential type. Do NOT loop on partial_update_workflow \u2014 switch approach instead.";
42002
42037
  function toMessage(error) {
42003
42038
  if (!error)
42004
42039
  return "";
@@ -42053,6 +42088,9 @@ function isExecutionTimeout(msg) {
42053
42088
  function isUnknownNode(msg) {
42054
42089
  return msg.includes("unknown node") || msg.includes("node type") && msg.includes("not found") || msg.includes("node type missing") || msg.includes("import error") || msg.includes("not a known node") || msg.includes("node not found");
42055
42090
  }
42091
+ function isMissingCredentialType(msg) {
42092
+ return msg.includes("missing required credential") || msg.includes("no credentials found for");
42093
+ }
42056
42094
  function isValidationError(msg) {
42057
42095
  return msg.includes("validation") || msg.includes("schema mismatch") || msg.includes("missing required") || msg.includes("missing field") || msg.includes("invalid parameter") || msg.includes("required property") || msg.includes("configuration issue") || // "N nodes have configuration issues" (n8n publish error)
42058
42096
  msg.includes("missing or invalid required");
@@ -42085,6 +42123,8 @@ function classifyFailure(error, context = {}) {
42085
42123
  return classify("config_error", context, context.nodeType);
42086
42124
  if (isSchemaError(msg))
42087
42125
  return classify("partial_fix_possible", context, void 0, SCHEMA_FORMAT_HINT);
42126
+ if (isMissingCredentialType(msg))
42127
+ return classify("credential_error", context, void 0, MISSING_CREDENTIAL_TYPE_HINT);
42088
42128
  if (isValidationError(msg))
42089
42129
  return classify("partial_fix_possible", context);
42090
42130
  if (isMultipleFailures(msg))
@@ -42291,15 +42331,6 @@ async function fetchWebhookExecution(apiConfig, workflowId) {
42291
42331
  return EMPTY_EXECUTION_DATA;
42292
42332
  }
42293
42333
  }
42294
- function annotateCloneDebug(debug) {
42295
- const hasErrors = Object.values(debug).some((v) => v?.status === "error" || v?.status === "failed");
42296
- if (!hasErrors)
42297
- return debug;
42298
- return {
42299
- ...debug,
42300
- _clone_test_note: "IMPORTANT: Node errors in this debug data are CLONE ARTIFACTS. The clone uses shared credentials which may not work identically. The overall test PASSED \u2014 do NOT fix these node-level errors."
42301
- };
42302
- }
42303
42334
  function extractExecutedNodeNames(execution) {
42304
42335
  const runData = execution?.data?.resultData?.runData;
42305
42336
  if (!runData || typeof runData !== "object")
@@ -42454,10 +42485,6 @@ async function executeWebhookTrigger(workflow, payload, timeoutMs, apiConfig) {
42454
42485
  }
42455
42486
  }
42456
42487
  }
42457
- var CLONE_TRIGGER_KEYWORDS = ["trigger", "webhook", "cron", "schedule", "polling"];
42458
- var WEBHOOK_INITIAL_WAIT_MS = 3e3;
42459
- var WEBHOOK_MAX_ATTEMPTS = 5;
42460
- var WEBHOOK_RETRY_DELAYS_MS = [1e3, 2e3, 4e3, 8e3];
42461
42488
  var RATE_LIMIT_MAX_RETRIES = 3;
42462
42489
  var RATE_LIMIT_RETRY_DELAYS_MS = [2e3, 4e3, 8e3];
42463
42490
  function isRateLimitError(error) {
@@ -42477,214 +42504,66 @@ function buildRateLimitedResult(triggerType, webhookPath) {
42477
42504
  next_step: "Credential is temporarily throttled. Do not debug with Bash. Wait 30s and retry test_workflow."
42478
42505
  };
42479
42506
  }
42480
- async function retryCloneWebhookOn429(webhookPath, testPayload) {
42481
- for (let retry = 0; retry < RATE_LIMIT_MAX_RETRIES; retry++) {
42482
- const delay = RATE_LIMIT_RETRY_DELAYS_MS[retry];
42483
- import_logging33.logger.info("test_workflow: clone 429 retry", { attempt: retry + 1, delay, webhookPath });
42484
- await new Promise((r) => setTimeout(r, delay));
42485
- try {
42486
- await postWebhook(buildN8nHostUrl(), webhookPath, testPayload, 15e3);
42487
- return "success";
42488
- } catch (retryErr) {
42489
- if (!isRateLimitError(retryErr))
42490
- return "success";
42491
- }
42492
- }
42493
- return "exhausted";
42494
- }
42495
- function extractCredentialIds(nodes) {
42496
- const ids = /* @__PURE__ */ new Set();
42497
- for (const node of nodes) {
42498
- if (!node.credentials || typeof node.credentials !== "object")
42499
- continue;
42500
- for (const cred of Object.values(node.credentials)) {
42501
- const id = cred?.id;
42502
- if (typeof id === "string" && id)
42503
- ids.add(id);
42504
- }
42505
- }
42506
- return [...ids];
42507
- }
42508
- function extractCredentialNames(nodes) {
42509
- const names = [];
42510
- for (const node of nodes) {
42511
- if (!node.credentials || typeof node.credentials !== "object")
42512
- continue;
42513
- for (const [credType, cred] of Object.entries(node.credentials)) {
42514
- const name = cred?.name;
42515
- names.push(typeof name === "string" && name ? `${name} (${credType})` : credType);
42516
- }
42517
- }
42518
- return [...new Set(names)];
42519
- }
42520
- async function testViaClone(workflow, triggerType, testPayload, timeoutMs, apiConfig) {
42521
- const hasR2W = await hasRespondToWebhookNode(workflow);
42522
- if (hasR2W) {
42523
- return {
42524
- execution_id: null,
42525
- status: "error",
42526
- trigger_type: triggerType,
42527
- result_summary: "Cannot test via clone: workflow contains a Respond to Webhook node. Test this workflow using test_workflow with a direct webhook POST.",
42528
- next_step: "This workflow has a Respond to Webhook node. Call test_workflow directly \u2014 it handles the response mode correctly.",
42529
- failure_type: "config_error",
42530
- recovery_hint: "Node configuration error. Review the workflow structure \u2014 Respond to Webhook nodes require direct webhook testing."
42531
- };
42532
- }
42533
- const nodes = workflow.nodes ?? [];
42534
- const triggerNode = nodes.find((n) => {
42535
- const type = String(n?.type ?? "").toLowerCase();
42536
- return CLONE_TRIGGER_KEYWORDS.some((kw) => type.includes(kw));
42537
- });
42538
- const triggerName = triggerNode?.name ?? "Manual Trigger";
42539
- const triggerPosition = triggerNode?.position ?? [250, 300];
42540
- const webhookPath = `test-clone-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
42541
- const cloneNodes = nodes.map((node) => {
42542
- if (node.name === triggerName) {
42543
- return {
42544
- parameters: { path: webhookPath, httpMethod: "POST", options: {} },
42545
- id: node.id ?? "webhook-clone",
42546
- name: "Webhook",
42547
- type: "n8n-nodes-base.webhook",
42548
- typeVersion: 2,
42549
- position: triggerPosition,
42550
- webhookId: (0, import_node_crypto.randomUUID)()
42551
- };
42552
- }
42553
- return node;
42554
- });
42555
- const connections = { ...workflow.connections ?? {} };
42556
- if (triggerName !== "Webhook" && connections[triggerName]) {
42557
- connections["Webhook"] = connections[triggerName];
42558
- delete connections[triggerName];
42559
- }
42560
- const cloneStartMs = Date.now();
42507
+ async function testViaDirectExecution(workflow, triggerType, _testPayload, timeoutMs, apiConfig) {
42561
42508
  const { calculateAdaptiveTimeout: calculateAdaptiveTimeout2 } = await Promise.resolve().then(() => (init_timeout(), timeout_exports));
42562
42509
  const adaptiveTimeout = calculateAdaptiveTimeout2(workflow);
42563
42510
  const effectiveTimeout = Math.max(timeoutMs, adaptiveTimeout);
42564
42511
  if (effectiveTimeout > timeoutMs) {
42565
- import_logging33.logger.info("test_workflow: adaptive timeout (clone)", {
42512
+ import_logging33.logger.info("test_workflow: adaptive timeout (direct)", {
42566
42513
  workflowId: workflow.id,
42567
42514
  requested: timeoutMs,
42568
42515
  adaptive: adaptiveTimeout,
42569
42516
  effective: effectiveTimeout
42570
42517
  });
42571
42518
  }
42572
- let cloneId = null;
42573
42519
  try {
42574
- const createResult = await createWorkflow(apiConfig, `_tmp-test-${workflow.id}`, {
42575
- nodes: cloneNodes,
42576
- connections,
42577
- settings: workflow.settings ?? { executionOrder: "v1" }
42578
- });
42579
- cloneId = createResult.id;
42580
- const credentialIds = extractCredentialIds(nodes);
42581
- await shareCredentialsWithWorkflow(apiConfig, credentialIds, cloneId);
42582
- const activateResult = await activateWorkflow(apiConfig, cloneId);
42583
- if (!activateResult.active) {
42584
- const credNames = extractCredentialNames(nodes);
42585
- const credDetail = credNames.length > 0 ? ` Required credentials: ${credNames.join(", ")}.` : "";
42520
+ const persistedWorkflow = await getWorkflow(apiConfig, workflow.id);
42521
+ const runResult = await executeWorkflowViaRest(apiConfig, workflow.id, persistedWorkflow);
42522
+ const executionId = runResult.executionId ?? runResult.id ?? null;
42523
+ if (!executionId) {
42586
42524
  return {
42587
42525
  execution_id: null,
42588
42526
  status: "error",
42589
42527
  trigger_type: triggerType,
42590
- result_summary: `Test clone could not be activated.${credDetail} Verify these credentials are configured and valid in n8n Settings \u2192 Credentials.`,
42591
- next_step: credNames.length > 0 ? `Check that credentials (${credNames.join(", ")}) are valid in n8n Settings \u2192 Credentials, then retry.` : "Check n8n Settings \u2192 Credentials and verify all required credentials are configured.",
42592
- failure_type: "credential_error",
42593
- recovery_hint: "Authentication failed. Call list_credentials to verify credential exists and is correct type."
42594
- };
42595
- }
42596
- await new Promise((r) => setTimeout(r, WEBHOOK_INITIAL_WAIT_MS));
42597
- let webhookRegistrationFailed = false;
42598
- let rateLimitExhausted = false;
42599
- for (let attempt = 0; attempt < WEBHOOK_MAX_ATTEMPTS; attempt++) {
42600
- try {
42601
- if (attempt > 0)
42602
- await new Promise((r) => setTimeout(r, WEBHOOK_RETRY_DELAYS_MS[attempt - 1]));
42603
- await postWebhook(buildN8nHostUrl(), webhookPath, testPayload, 15e3);
42604
- break;
42605
- } catch (webhookErr) {
42606
- const errMsg = webhookErr instanceof Error ? webhookErr.message : String(webhookErr);
42607
- const is404 = errMsg.includes("404") || errMsg.includes("not found");
42608
- const isTimeout = errMsg.includes("timeout");
42609
- if (isTimeout)
42610
- break;
42611
- if (is404 && attempt < WEBHOOK_MAX_ATTEMPTS - 1) {
42612
- import_logging33.logger.debug("test_workflow: webhook 404, retrying activation race", { attempt, webhookPath });
42613
- continue;
42614
- }
42615
- if (is404) {
42616
- webhookRegistrationFailed = true;
42617
- break;
42618
- }
42619
- if (isRateLimitError(webhookErr)) {
42620
- const rlResult = await retryCloneWebhookOn429(webhookPath, testPayload);
42621
- if (rlResult === "exhausted") {
42622
- rateLimitExhausted = true;
42623
- break;
42624
- }
42625
- if (rlResult === "success")
42626
- break;
42627
- }
42628
- throw webhookErr;
42629
- }
42630
- }
42631
- if (rateLimitExhausted) {
42632
- return buildRateLimitedResult(triggerType, webhookPath);
42633
- }
42634
- if (webhookRegistrationFailed) {
42635
- return {
42636
- execution_id: null,
42637
- status: "error",
42638
- trigger_type: triggerType,
42639
- result_summary: `Clone webhook "POST ${webhookPath}" failed to register after 5 retries (~18s). n8n may be under heavy load from concurrent processes.`,
42640
- next_step: "Clone webhook failed to register after retries. Do NOT use Bash to retry manually. Report workflow as built-but-untested and stop.",
42641
- failure_type: "config_error",
42642
- recovery_hint: "Unclassified error. Review error message. Consider get_node_info for the failing node."
42528
+ result_summary: "Direct REST execution did not return an execution ID.",
42529
+ next_step: "Retry test_workflow once. If the workflow still does not return an execution ID, stop and inspect the n8n instance.",
42530
+ failure_type: "execution_timeout",
42531
+ recovery_hint: "Internal REST execution did not return an execution ID. Retry test_workflow once; avoid rebuilding unless the workflow structure is clearly wrong."
42643
42532
  };
42644
42533
  }
42645
- await new Promise((r) => setTimeout(r, 1e3));
42646
- const SAFETY_CAP_MS = 6e5;
42647
- const safetyDeadline = Date.now() + SAFETY_CAP_MS;
42648
- import_logging33.logger.info("test_workflow: polling for clone execution (state-driven)", {
42534
+ const safetyDeadline = Date.now() + effectiveTimeout;
42535
+ import_logging33.logger.info("test_workflow: polling for direct execution via REST run", {
42649
42536
  workflowId: workflow.id,
42650
- cloneId,
42651
- safetyCap: "10min"
42537
+ executionId,
42538
+ safetyCapMs: effectiveTimeout
42652
42539
  });
42653
- let runningExecId = null;
42654
- let latestExecId = null;
42655
- let cloneExecFailed = false;
42540
+ let executionFailed = false;
42656
42541
  let nodeDebug;
42657
42542
  while (Date.now() < safetyDeadline) {
42658
- const execList = await listExecutions(apiConfig, { workflowId: cloneId, limit: 1 });
42659
- const latest = execList.executions[0];
42660
- if (latest?.id) {
42661
- runningExecId = latest.id;
42662
- const fullExec = await getExecution(apiConfig, latest.id, true);
42663
- if (fullExec.finished || fullExec.status === "error" || fullExec.status === "success") {
42664
- latestExecId = latest.id;
42665
- cloneExecFailed = fullExec.status === "error";
42666
- nodeDebug = extractNodeDebugData(fullExec);
42667
- break;
42668
- }
42543
+ const fullExec = await getExecution(apiConfig, executionId, true);
42544
+ if (fullExec.finished || fullExec.status === "error" || fullExec.status === "success") {
42545
+ executionFailed = fullExec.status === "error";
42546
+ nodeDebug = extractNodeDebugData(fullExec);
42547
+ break;
42669
42548
  }
42670
42549
  await new Promise((r) => setTimeout(r, 1e3));
42671
42550
  }
42672
- if (runningExecId && !latestExecId) {
42551
+ if (!nodeDebug && Date.now() >= safetyDeadline) {
42673
42552
  try {
42674
- await deleteExecution(apiConfig, runningExecId);
42675
- import_logging33.logger.warn("test_workflow: cancelled execution at safety cap (10min)", {
42553
+ await deleteExecution(apiConfig, executionId);
42554
+ import_logging33.logger.warn("test_workflow: cancelled direct execution at safety cap", {
42676
42555
  workflowId: workflow.id,
42677
- executionId: runningExecId
42556
+ executionId
42678
42557
  });
42679
42558
  } catch (cancelErr) {
42680
- import_logging33.logger.warn("test_workflow: failed to cancel execution", {
42559
+ import_logging33.logger.warn("test_workflow: failed to cancel direct execution", {
42681
42560
  workflowId: workflow.id,
42682
- executionId: runningExecId,
42561
+ executionId,
42683
42562
  error: cancelErr
42684
42563
  });
42685
42564
  }
42686
42565
  }
42687
- if (latestExecId && cloneExecFailed && nodeDebug) {
42566
+ if (executionFailed && nodeDebug) {
42688
42567
  const failedEntry = Object.entries(nodeDebug).find(([_, v]) => v?.status === "error");
42689
42568
  if (failedEntry) {
42690
42569
  const [nodeName, nodeData] = failedEntry;
@@ -42692,75 +42571,57 @@ async function testViaClone(workflow, triggerType, testPayload, timeoutMs, apiCo
42692
42571
  const nodeErrMsg = errInfo?.message ?? "Unknown error";
42693
42572
  const taxonomy = classifyFailure(errInfo ?? nodeErrMsg, { nodeName, httpCode: errInfo?.httpCode });
42694
42573
  return {
42695
- execution_id: latestExecId,
42574
+ execution_id: executionId,
42696
42575
  status: "execution_error",
42697
42576
  trigger_type: triggerType,
42698
42577
  failed_node: nodeName,
42699
42578
  node_error: nodeErrMsg,
42700
42579
  node_error_description: errInfo?.description,
42701
- result_summary: `Clone test execution FAILED at node "${nodeName}": ${nodeErrMsg}`,
42702
- next_step: `Fix the "${nodeName}" node configuration in the original workflow (${workflow.id}), then retry test_workflow.`,
42580
+ result_summary: `Workflow execution FAILED at node "${nodeName}": ${nodeErrMsg}`,
42581
+ next_step: `Fix the "${nodeName}" node configuration in workflow ${workflow.id}, then retry test_workflow.`,
42703
42582
  ...taxonomy
42704
42583
  };
42705
42584
  }
42706
42585
  }
42707
- if (latestExecId && !cloneExecFailed) {
42708
- try {
42709
- await activateWorkflow(apiConfig, workflow.id);
42710
- import_logging33.logger.info("test_workflow: original activated after clone success", { originalId: workflow.id });
42711
- } catch {
42712
- }
42586
+ if (!nodeDebug) {
42587
+ return {
42588
+ execution_id: executionId,
42589
+ status: "error",
42590
+ trigger_type: triggerType,
42591
+ result_summary: `Workflow execution did not complete within timeout for workflow ${workflow.id}.`,
42592
+ next_step: "Retry test_workflow once. If the workflow still times out, inspect the instance or run manually in n8n.",
42593
+ failure_type: "execution_timeout",
42594
+ recovery_hint: "Execution timed out before reaching a terminal state. Retry test_workflow once; avoid rebuilding unless the workflow structure is clearly wrong."
42595
+ };
42713
42596
  }
42714
- const annotatedDebug = latestExecId && nodeDebug ? annotateCloneDebug(nodeDebug) : nodeDebug;
42715
42597
  return {
42716
- execution_id: latestExecId,
42717
- status: latestExecId ? "success" : "error",
42598
+ execution_id: executionId,
42599
+ status: "success",
42718
42600
  trigger_type: triggerType,
42719
- result_summary: latestExecId ? `Workflow tested via temporary clone (${triggerType} trigger cannot be executed directly \u2014 a webhook clone was used to test the workflow logic). Original workflow (${workflow.id}) is now active.` : `Clone execution did not complete within timeout. The workflow uses a ${triggerType} trigger which was tested via temporary webhook clone. Original workflow (${workflow.id}) was not modified.`,
42720
- next_step: latestExecId ? "Test passed. The workflow is working correctly and active. No further changes needed. Do NOT call get_execution_result \u2014 the test clone has been cleaned up." : `Clone timed out. Check if the workflow logic is correct, then call activate_workflow with workflow_id "${workflow.id}".`,
42721
- raw_result: annotatedDebug ? { node_debug: annotatedDebug } : void 0,
42722
- ...!latestExecId ? { failure_type: "execution_timeout", recovery_hint: "Execution timed out. Use get_execution_result to check status. Workflow may be too slow or waiting for external input." } : {}
42601
+ result_summary: `Workflow tested successfully via direct REST execution on workflow ${workflow.id}.`,
42602
+ next_step: "Test passed. The workflow is working correctly. No further changes needed.",
42603
+ raw_result: nodeDebug ? { node_debug: nodeDebug } : void 0
42723
42604
  };
42724
42605
  } catch (err) {
42725
42606
  const errMsg = err instanceof Error ? err.message : String(err);
42607
+ import_logging33.logger.warn("test_workflow: direct execution failed", {
42608
+ workflowId: workflow.id,
42609
+ error: errMsg
42610
+ });
42726
42611
  return {
42727
42612
  execution_id: null,
42728
42613
  status: "error",
42729
42614
  trigger_type: triggerType,
42730
- result_summary: `Clone test failed: ${errMsg}. Original workflow (${workflow.id}) was not modified.`,
42615
+ result_summary: `Direct workflow execution failed: ${errMsg}.`,
42731
42616
  ...classifyFailure(err, {})
42732
42617
  };
42733
- } finally {
42734
- if (cloneId) {
42735
- try {
42736
- await deleteWorkflow(apiConfig, cloneId);
42737
- } catch {
42738
- }
42739
- }
42740
42618
  }
42741
42619
  }
42742
- function buildGuidanceResult(triggerType) {
42743
- const messages = {
42744
- manual: "Activate the workflow, then trigger it manually in the n8n UI.",
42745
- schedule: "Clone-based testing is disabled. Use execute_workflow to test schedule workflows on demand via clone-and-execute.",
42746
- polling: "Clone-based testing is disabled. Use execute_workflow to test polling workflows on demand via clone-and-execute.",
42747
- webhook: ""
42748
- };
42749
- return {
42750
- execution_id: null,
42751
- status: "guidance",
42752
- trigger_type: triggerType,
42753
- result_summary: messages[triggerType]
42754
- };
42755
- }
42756
42620
  async function routeTestExecution(workflow, triggerType, testPayload, timeoutMs, apiConfig) {
42757
42621
  if (triggerType === "webhook") {
42758
42622
  return executeWebhookTrigger(workflow, testPayload, timeoutMs, apiConfig);
42759
42623
  }
42760
- if (process.env.CLONE_EXECUTION_ENABLED === "false") {
42761
- return buildGuidanceResult(triggerType);
42762
- }
42763
- return testViaClone(workflow, triggerType, testPayload, timeoutMs, apiConfig);
42624
+ return testViaDirectExecution(workflow, triggerType, testPayload, timeoutMs, apiConfig);
42764
42625
  }
42765
42626
  var activeTests = /* @__PURE__ */ new Set();
42766
42627
  function buildConcurrentTestError(workflowId) {
@@ -42837,7 +42698,7 @@ and deactivates afterward. No need to construct URLs or use curl.
42837
42698
 
42838
42699
  Example: test_workflow({ workflow_id: "abc123", test_payload: { name: "Test", email: "test@example.com" } })
42839
42700
 
42840
- **Schedule/manual/polling:** These triggers cannot be executed directly via API. The tool automatically creates a temporary webhook clone of the workflow to test the logic, then cleans up. The original workflow is NOT modified. Timeout adapts to workflow complexity.
42701
+ **Schedule/manual/polling:** The tool executes the saved workflow directly via n8n's internal REST run path and polls the real execution. This tests the actual workflow with minimal overhead.
42841
42702
 
42842
42703
  Returns: { execution_id, status, trigger_type, result_summary, http_status, webhook_response, execution_time_ms, nodes_executed, failed_node, node_error, node_error_description }
42843
42704
  When status is "execution_error": the webhook POST succeeded (HTTP 2xx) but the workflow execution failed. failed_node contains the node name, node_error the error message. Fix the node and retry.
@@ -42878,7 +42739,7 @@ Follow the recovery_hint instead of using Bash commands.`,
42878
42739
 
42879
42740
  // dist/mcp/tools/handlers/execute-workflow.js
42880
42741
  var DEFAULT_POLL_INTERVAL_MS = 500;
42881
- var CLONE_TRIGGER_KEYWORDS2 = ["trigger", "webhook", "cron", "schedule", "polling"];
42742
+ var CLONE_TRIGGER_KEYWORDS = ["trigger", "webhook", "cron", "schedule", "polling"];
42882
42743
  function buildApiConfig3(timeoutMs) {
42883
42744
  return {
42884
42745
  baseUrl: process.env.N8N_API_URL ?? process.env.N8N_API_BASE_URL ?? "http://localhost:5678/api/v1",
@@ -42912,7 +42773,7 @@ function findTriggerNodeType(workflow) {
42912
42773
  const nodes = workflow.nodes ?? [];
42913
42774
  const trigger = nodes.find((n) => {
42914
42775
  const type = String(n?.type ?? "").toLowerCase();
42915
- return CLONE_TRIGGER_KEYWORDS2.some((kw) => type.includes(kw));
42776
+ return CLONE_TRIGGER_KEYWORDS.some((kw) => type.includes(kw));
42916
42777
  });
42917
42778
  return trigger ? String(trigger.type ?? "") : null;
42918
42779
  }
@@ -43053,11 +42914,11 @@ function buildCloneWorkflow(original) {
43053
42914
  const nodes = original.nodes ?? [];
43054
42915
  const triggerNode = nodes.find((n) => {
43055
42916
  const type = String(n?.type ?? "").toLowerCase();
43056
- return CLONE_TRIGGER_KEYWORDS2.some((kw) => type.includes(kw));
42917
+ return CLONE_TRIGGER_KEYWORDS.some((kw) => type.includes(kw));
43057
42918
  });
43058
42919
  const triggerName = triggerNode?.name ?? "Manual Trigger";
43059
42920
  const triggerPosition = triggerNode?.position ?? [250, 300];
43060
- const webhookPath = `clone-exec-${(0, import_node_crypto2.randomUUID)().slice(0, 8)}`;
42921
+ const webhookPath = `clone-exec-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
43061
42922
  const cloneNodes = nodes.map((node) => {
43062
42923
  if (node.name === triggerName) {
43063
42924
  return {
@@ -43071,7 +42932,7 @@ function buildCloneWorkflow(original) {
43071
42932
  type: "n8n-nodes-base.webhook",
43072
42933
  typeVersion: 2,
43073
42934
  position: triggerPosition,
43074
- webhookId: (0, import_node_crypto2.randomUUID)()
42935
+ webhookId: (0, import_node_crypto.randomUUID)()
43075
42936
  };
43076
42937
  }
43077
42938
  return node;
@@ -50624,7 +50485,6 @@ function substituteDeprecatedAiNodes(nodes, connections) {
50624
50485
 
50625
50486
  // dist/mcp/tools/handlers/build-workflow.js
50626
50487
  init_n8n_api_adapter();
50627
- var import_node_crypto3 = require("node:crypto");
50628
50488
  init_timeout();
50629
50489
  var import_logging67 = __toESM(require_logging(), 1);
50630
50490
 
@@ -51815,10 +51675,20 @@ var SKIP_NODE_TYPE_PATTERNS = [
51815
51675
  function isSkipNode(nodeType) {
51816
51676
  return SKIP_NODE_TYPE_PATTERNS.some((p) => nodeType.toLowerCase() === p.toLowerCase());
51817
51677
  }
51678
+ var CREDENTIAL_TYPE_ALIASES = {
51679
+ // googleCalendarOAuth2Api: removed — real OAuth2 credential now provisioned
51680
+ googleSheetsOAuth2Api: ["googleApi"],
51681
+ googleDriveOAuth2Api: ["googleApi"],
51682
+ googleDocsOAuth2Api: ["googleApi"],
51683
+ googleAnalyticsOAuth2Api: ["googleApi"],
51684
+ googleTasksOAuth2Api: ["googleApi"],
51685
+ googleGmailOAuth2Api: ["googleApi"]
51686
+ };
51818
51687
  async function buildWirePlan(nodes, credentialCache) {
51819
51688
  const bundle = await initPublicCatalogBundle();
51820
51689
  const plans = [];
51821
51690
  const ambiguous = [];
51691
+ const unwired = [];
51822
51692
  for (const node of nodes) {
51823
51693
  if (hasCredentials(node))
51824
51694
  continue;
@@ -51831,7 +51701,22 @@ async function buildWirePlan(nodes, credentialCache) {
51831
51701
  const credType = required[0].credential_type;
51832
51702
  const candidates = credentialCache.filter((c) => c.type === credType || c.type.toLowerCase() === credType.toLowerCase());
51833
51703
  if (candidates.length === 0) {
51834
- import_logging66.logger.debug("credential-auto-wire: no match for type", { node: node.name, credType });
51704
+ const aliases = CREDENTIAL_TYPE_ALIASES[credType] ?? [];
51705
+ let wiredViaAlias = false;
51706
+ for (const aliasType of aliases) {
51707
+ const aliasCandidates = credentialCache.filter((c) => c.type === aliasType);
51708
+ if (aliasCandidates.length > 0) {
51709
+ const picked2 = pickBestCredential(aliasCandidates, node.type);
51710
+ plans.push({ nodeName: node.name, credentialType: aliasType, credential: picked2 });
51711
+ import_logging66.logger.debug("credential-auto-wire: wired via alias", { node: node.name, credType, aliasType });
51712
+ wiredViaAlias = true;
51713
+ break;
51714
+ }
51715
+ }
51716
+ if (!wiredViaAlias) {
51717
+ import_logging66.logger.debug("credential-auto-wire: no match for type or aliases", { node: node.name, credType });
51718
+ unwired.push({ node: node.name, required_type: credType });
51719
+ }
51835
51720
  continue;
51836
51721
  }
51837
51722
  if (candidates.length === 1) {
@@ -51851,7 +51736,7 @@ async function buildWirePlan(nodes, credentialCache) {
51851
51736
  });
51852
51737
  }
51853
51738
  }
51854
- return { plans, ambiguous };
51739
+ return { plans, ambiguous, unwired };
51855
51740
  }
51856
51741
  async function patchWorkflowCredentials(workflowId, config2, plans) {
51857
51742
  const workflow = await getWorkflow(config2, workflowId);
@@ -51875,9 +51760,9 @@ async function autoWireCredentials2(workflowId, deployedNodes, config2) {
51875
51760
  try {
51876
51761
  const credentialCache = await getPreloadedCredentials();
51877
51762
  if (credentialCache.length === 0) {
51878
- return { wired: [], ambiguous: [] };
51763
+ return { wired: [], ambiguous: [], unwired: [] };
51879
51764
  }
51880
- const { plans, ambiguous } = await buildWirePlan(deployedNodes, credentialCache);
51765
+ const { plans, ambiguous, unwired } = await buildWirePlan(deployedNodes, credentialCache);
51881
51766
  if (plans.length > 0) {
51882
51767
  await patchWorkflowCredentials(workflowId, config2, plans);
51883
51768
  import_logging66.logger.info("credential-auto-wire: wired credentials", {
@@ -51891,13 +51776,13 @@ async function autoWireCredentials2(workflowId, deployedNodes, config2) {
51891
51776
  credential: p.credential.name,
51892
51777
  type: p.credentialType
51893
51778
  }));
51894
- return { wired, ambiguous };
51779
+ return { wired, ambiguous, unwired };
51895
51780
  } catch (err) {
51896
51781
  import_logging66.logger.warn("credential-auto-wire: failed (non-fatal), continuing without wiring", {
51897
51782
  workflowId,
51898
51783
  error: err instanceof Error ? err.message : String(err)
51899
51784
  });
51900
- return { wired: [], ambiguous: [] };
51785
+ return { wired: [], ambiguous: [], unwired: [] };
51901
51786
  }
51902
51787
  }
51903
51788
 
@@ -51931,13 +51816,28 @@ function buildUnfixableIssues(validation) {
51931
51816
  }
51932
51817
  async function executeWebhookTest(path3, payload, timeoutMs, start) {
51933
51818
  try {
51934
- const out = await withTimeout(postWebhookTest(buildN8nHostUrl(), path3, payload ?? {}, timeoutMs), timeoutMs, "build_workflow:test");
51819
+ const out = await withTimeout(postWebhookDetailed(buildN8nHostUrl(), path3, payload ?? {}, timeoutMs), timeoutMs, "build_workflow:test");
51935
51820
  return { success: true, output: out, duration_ms: Math.round(performance.now() - start), method: "webhook_inline" };
51936
51821
  } catch (err) {
51937
51822
  return { success: false, error: err instanceof Error ? err.message : String(err), duration_ms: Math.round(performance.now() - start), method: "webhook_inline" };
51938
51823
  }
51939
51824
  }
51940
- var BUILD_CLONE_TRIGGER_KEYWORDS = ["trigger", "webhook", "cron", "schedule", "polling"];
51825
+ function buildCloneLifecycleSummary(startMs, telemetry) {
51826
+ const summary = {};
51827
+ if (telemetry.clone_created_at)
51828
+ summary.clone_create_ms = telemetry.clone_created_at - startMs;
51829
+ if (telemetry.clone_activated_at)
51830
+ summary.clone_activate_ms = telemetry.clone_activated_at - startMs;
51831
+ if (telemetry.clone_publish_ready_at)
51832
+ summary.clone_publish_ready_ms = telemetry.clone_publish_ready_at - startMs;
51833
+ if (telemetry.webhook_fired_at)
51834
+ summary.webhook_fire_ms = telemetry.webhook_fired_at - startMs;
51835
+ if (telemetry.first_poll_at)
51836
+ summary.first_poll_ms = telemetry.first_poll_at - startMs;
51837
+ if (telemetry.first_execution_seen_at)
51838
+ summary.first_execution_seen_ms = telemetry.first_execution_seen_at - startMs;
51839
+ return summary;
51840
+ }
51941
51841
  function buildCloneApiConfig(timeoutMs) {
51942
51842
  return {
51943
51843
  baseUrl: process.env.N8N_TEST_API_URL ?? process.env.N8N_API_URL ?? process.env.N8N_API_BASE_URL ?? "http://localhost:5678/api/v1",
@@ -51945,116 +51845,68 @@ function buildCloneApiConfig(timeoutMs) {
51945
51845
  timeoutMs
51946
51846
  };
51947
51847
  }
51948
- function buildCloneSpec(nodes, connections, settings) {
51949
- const triggerNode = nodes.find((n) => {
51950
- const type = String(n?.type ?? "").toLowerCase();
51951
- return BUILD_CLONE_TRIGGER_KEYWORDS.some((kw) => type.includes(kw));
51952
- });
51953
- const triggerName = triggerNode?.name ?? "Manual Trigger";
51954
- const webhookPath = `build-test-${(0, import_node_crypto3.randomUUID)().slice(0, 8)}`;
51955
- const cloneNodes = nodes.map((node) => node.name === triggerName ? { parameters: { path: webhookPath, httpMethod: "POST", options: {} }, id: node.id ?? "webhook-clone", name: "Webhook", type: "n8n-nodes-base.webhook", typeVersion: 2, position: node.position ?? [250, 300], webhookId: (0, import_node_crypto3.randomUUID)() } : node);
51956
- const cloneConnections = { ...connections };
51957
- if (triggerName !== "Webhook" && cloneConnections[triggerName]) {
51958
- cloneConnections["Webhook"] = cloneConnections[triggerName];
51959
- delete cloneConnections[triggerName];
51960
- }
51961
- return { cloneNodes, cloneConnections, webhookPath };
51962
- }
51963
- async function postCloneWebhook(webhookPath, payload) {
51964
- for (let attempt = 0; attempt < 3; attempt++) {
51965
- try {
51966
- if (attempt > 0)
51967
- await new Promise((r) => setTimeout(r, 1e3 * attempt));
51968
- await postWebhook(buildN8nHostUrl(), webhookPath, payload, 15e3);
51969
- return;
51970
- } catch (err) {
51971
- const msg = err instanceof Error ? err.message : String(err);
51972
- if (msg.includes("timeout"))
51973
- return;
51974
- if (!msg.includes("404") || attempt === 2)
51975
- throw err;
51976
- }
51977
- }
51978
- }
51979
- async function pollCloneExecution(config2, cloneId, budgetMs) {
51848
+ async function pollDirectExecution(config2, executionId, budgetMs, telemetry, start) {
51980
51849
  const deadline = Date.now() + budgetMs;
51981
51850
  while (Date.now() < deadline) {
51982
- const list = await listExecutions(config2, { workflowId: cloneId, limit: 1 });
51983
- const latest = list.executions[0];
51984
- if (latest?.id) {
51985
- const full = await getExecution(config2, latest.id, true);
51986
- if (full.finished || full.status === "error" || full.status === "crashed") {
51987
- const hasError = full.status === "error" || full.status === "crashed" || full.error != null;
51988
- return { execId: latest.id, execStatus: hasError ? "error" : "success", nodeDebug: extractNodeDebugData(full) };
51989
- }
51851
+ if (!telemetry.first_poll_at)
51852
+ telemetry.first_poll_at = Date.now();
51853
+ const full = await getExecution(config2, executionId, true);
51854
+ if (!telemetry.first_execution_seen_at) {
51855
+ telemetry.first_execution_seen_at = Date.now();
51856
+ import_logging67.logger.info("build_workflow: direct execution became visible", {
51857
+ executionId,
51858
+ ...buildCloneLifecycleSummary(start, telemetry)
51859
+ });
51860
+ }
51861
+ if (full.finished || full.status === "error" || full.status === "crashed" || full.status === "success") {
51862
+ const hasError = full.status === "error" || full.status === "crashed" || full.error != null;
51863
+ return { execId: executionId, execStatus: hasError ? "error" : "success", nodeDebug: extractNodeDebugData(full) };
51990
51864
  }
51991
51865
  await new Promise((r) => setTimeout(r, 500));
51992
51866
  }
51993
51867
  return { execId: null, execStatus: "timeout" };
51994
51868
  }
51995
- async function runCloneExecution(config2, cloneId, webhookPath, testPayload, effectiveTimeout, start) {
51996
- const activated = await activateWorkflow(config2, cloneId);
51997
- if (!activated.active) {
51998
- return { success: false, error: "Clone could not be activated \u2014 check credentials", duration_ms: Math.round(performance.now() - start), method: "clone_and_execute" };
51869
+ async function runDirectExecution(config2, workflowId, persistedWorkflow, effectiveTimeout, start) {
51870
+ const telemetry = { clone_created_at: Date.now() };
51871
+ const runResult = await executeWorkflowViaRest(config2, workflowId, persistedWorkflow);
51872
+ telemetry.webhook_fired_at = Date.now();
51873
+ const executionId = runResult.executionId ?? runResult.id ?? null;
51874
+ if (!executionId) {
51875
+ return {
51876
+ success: false,
51877
+ error: "Direct execution did not return an execution ID via REST run",
51878
+ duration_ms: Math.round(performance.now() - start),
51879
+ method: "direct_execute"
51880
+ };
51999
51881
  }
52000
- await new Promise((r) => setTimeout(r, 3e3));
52001
- const payload = testPayload ?? { test: true };
52002
- await postCloneWebhook(webhookPath, payload);
52003
- await new Promise((r) => setTimeout(r, 1500));
52004
51882
  const remaining = Math.max(effectiveTimeout - (performance.now() - start), 500);
52005
- const { execId, execStatus, nodeDebug } = await pollCloneExecution(config2, cloneId, remaining);
51883
+ const { execId, execStatus, nodeDebug } = await pollDirectExecution(config2, executionId, remaining, telemetry, start);
52006
51884
  return {
52007
51885
  success: execStatus === "success",
52008
- output: execId ? `Clone test ${execStatus}` : "Clone test timed out",
51886
+ output: execId ? `Direct test ${execStatus}` : "Direct test timed out",
51887
+ error: execStatus === "timeout" ? "Direct execution timed out before reaching a terminal state" : void 0,
52009
51888
  duration_ms: Math.round(performance.now() - start),
52010
51889
  execution_id: execId ?? void 0,
52011
51890
  node_debug: nodeDebug,
52012
- method: "clone_and_execute"
51891
+ method: "direct_execute"
52013
51892
  };
52014
51893
  }
52015
- async function executeCloneTest(workflowId, testPayload, timeoutMs) {
51894
+ async function executeDirectTriggerTest(workflowId, timeoutMs) {
52016
51895
  const start = performance.now();
52017
51896
  const config2 = buildCloneApiConfig(timeoutMs);
52018
51897
  const workflow = await getWorkflow(config2, workflowId);
52019
- const nodes = workflow.nodes ?? [];
52020
- const hasR2W = await hasRespondToWebhookNode(workflow);
52021
- if (hasR2W) {
52022
- return {
52023
- success: false,
52024
- error: "Cannot test via clone: workflow contains a Respond to Webhook node. Use direct webhook POST (test_workflow) instead.",
52025
- duration_ms: Math.round(performance.now() - start),
52026
- method: "clone_and_execute"
52027
- };
52028
- }
52029
- const connections = workflow.connections ?? {};
52030
- const { cloneNodes, cloneConnections, webhookPath } = buildCloneSpec(nodes, connections, workflow.settings);
52031
51898
  const { calculateAdaptiveTimeout: calculateAdaptiveTimeout2 } = await Promise.resolve().then(() => (init_timeout(), timeout_exports));
52032
51899
  const adaptiveTimeout = calculateAdaptiveTimeout2(workflow);
52033
51900
  const effectiveTimeout = Math.max(timeoutMs, adaptiveTimeout);
52034
- let cloneId = null;
52035
51901
  try {
52036
- const created = await createWorkflow(config2, `_tmp-build-test-${workflowId}`, {
52037
- nodes: cloneNodes,
52038
- connections: cloneConnections,
52039
- settings: workflow.settings ?? { executionOrder: "v1" }
52040
- });
52041
- cloneId = created.id;
52042
- return await runCloneExecution(config2, cloneId, webhookPath, testPayload, effectiveTimeout, start);
51902
+ return await runDirectExecution(config2, workflowId, workflow, effectiveTimeout, start);
52043
51903
  } catch (err) {
52044
- return { success: false, error: `Clone test failed: ${err.message}`, duration_ms: Math.round(performance.now() - start), method: "clone_and_execute" };
52045
- } finally {
52046
- if (cloneId) {
52047
- try {
52048
- await deactivateWorkflow(config2, cloneId).catch(() => {
52049
- });
52050
- } catch {
52051
- }
52052
- try {
52053
- await deleteWorkflow(config2, cloneId).catch(() => {
52054
- });
52055
- } catch {
52056
- }
52057
- }
51904
+ return {
51905
+ success: false,
51906
+ error: `Direct test failed: ${err.message}`,
51907
+ duration_ms: Math.round(performance.now() - start),
51908
+ method: "direct_execute"
51909
+ };
52058
51910
  }
52059
51911
  }
52060
51912
  async function retryWebhookTest(path3, testPayload, timeoutMs, start) {
@@ -52093,12 +51945,12 @@ async function runTestStep(workflow, workflowId, testPayload, timeoutMs) {
52093
51945
  import_logging67.logger.info("build_workflow: Respond to Webhook detected, retrying direct POST (no clone)", { workflowId });
52094
51946
  return retryWebhookTest(path3, testPayload, timeoutMs, start);
52095
51947
  }
52096
- import_logging67.logger.info("build_workflow: webhook 404, falling back to clone-and-execute", { workflowId });
52097
- return executeCloneTest(workflowId, testPayload, timeoutMs);
51948
+ import_logging67.logger.info("build_workflow: webhook 404, retrying direct POST with backoff", { workflowId });
51949
+ return retryWebhookTest(path3, testPayload, timeoutMs, start);
52098
51950
  }
52099
51951
  return testResult;
52100
51952
  }
52101
- return executeCloneTest(workflowId, testPayload, timeoutMs);
51953
+ return executeDirectTriggerTest(workflowId, timeoutMs);
52102
51954
  }
52103
51955
  async function deployWorkflow2(workflow, activate, timeoutMs) {
52104
51956
  const config2 = buildApiConfig10(timeoutMs);
@@ -52563,13 +52415,10 @@ function buildRichResponseFields(result, cleanedArgs, exec) {
52563
52415
  };
52564
52416
  } else if (exec?.tested && !exec.success) {
52565
52417
  parts.push(`Test: \u274C failed \u2014 ${exec.error ?? "unknown error"}`);
52566
- const taxonomy = classifyFailure(exec.error ?? "", {});
52567
52418
  result.test_result = {
52568
52419
  status: "error",
52569
52420
  execution_id: exec.execution_id,
52570
- duration_ms: exec.duration_ms,
52571
- failure_type: taxonomy.failure_type,
52572
- recovery_hint: taxonomy.recovery_hint
52421
+ duration_ms: exec.duration_ms
52573
52422
  };
52574
52423
  } else {
52575
52424
  result.test_result = { status: "skipped" };
@@ -52900,11 +52749,9 @@ async function handleBuildWorkflowStandard(args, correlationId, startTime) {
52900
52749
  result.next_step = `Workflow deployed and tested successfully (${exec.method}). Ready for production use.`;
52901
52750
  } else if (exec?.tested && !exec.success) {
52902
52751
  const taxonomy = classifyFailure(exec.error ?? "", {});
52903
- const retryHint = hasWebhookTrigger ? `Call test_workflow with workflow_id "${wfId}" and test_payload: {\u2026} to retry with a custom webhook payload.` : `Call execute_workflow with workflow_id "${wfId}" to retry after fixing.`;
52752
+ const retryHint = exec.error?.includes("no execution became visible") || exec.error?.includes("did not return an execution ID") ? `Call test_workflow with workflow_id "${wfId}" to retry once the n8n instance is responsive.` : hasWebhookTrigger ? `Call test_workflow with workflow_id "${wfId}" and test_payload: {\u2026} to retry with a custom webhook payload.` : `Call execute_workflow with workflow_id "${wfId}" to retry after fixing.`;
52904
52753
  const failureCtx = taxonomy.failure_type !== "config_error" ? `${taxonomy.recovery_hint} ` : "";
52905
52754
  result.next_step = `Workflow deployed but test FAILED [${taxonomy.failure_type}]: ${exec.error ?? "unknown error"}. ${failureCtx}${retryHint}`;
52906
- result.failure_type = taxonomy.failure_type;
52907
- result.recovery_hint = taxonomy.recovery_hint;
52908
52755
  } else if (hasWebhookTrigger) {
52909
52756
  result.next_step = `If you have not yet tested this workflow, call test_workflow with workflow_id "${wfId}" and test_payload: { /* your webhook JSON */ }. If test_workflow already returned test_passed=true and you have NOT called partial_update_workflow since, the workflow is complete \u2014 call activate_workflow. Do NOT use Bash/curl \u2014 test_workflow handles activation, URL resolution, and response capture automatically.`;
52910
52757
  } else {
@@ -52946,11 +52793,6 @@ CRITICAL RULES:
52946
52793
  Pipeline: assemble \u2192 validate (L1-L6 + 33 fixers) \u2192 deploy to n8n.
52947
52794
  Returns: workflow_id, workflow_url, validation changelog, optional test result.
52948
52795
 
52949
- When test_result.status is "error", the response also includes:
52950
- - failure_type: one of "config_error", "credential_error", "rate_limited", "execution_timeout", "partial_fix_possible", "rebuild_required"
52951
- - recovery_hint: specific guidance on what to do next
52952
- Follow the recovery_hint instead of using Bash commands.
52953
-
52954
52796
  Examples:
52955
52797
  - Fused: { description: "Send a Telegram message when a new Gmail arrives" }
52956
52798
  - Schedule: { name: "Daily Slack", nodes: [{name: "Schedule", type: "n8n-nodes-base.scheduleTrigger", parameters: {rule: {interval: [{field: "cronExpression", expression: "0 8 * * *"}]}}}, {name: "Slack", type: "n8n-nodes-base.slack", operation: "postMessage", credential_id: "abc"}], connections: [{from: "Schedule", to: "Slack"}] }
@@ -54324,7 +54166,7 @@ connection problems.`,
54324
54166
  var import_node_fs2 = require("node:fs");
54325
54167
  var import_node_os2 = require("node:os");
54326
54168
  var import_node_path2 = require("node:path");
54327
- var import_node_crypto4 = require("node:crypto");
54169
+ var import_node_crypto2 = require("node:crypto");
54328
54170
  var import_logging73 = __toESM(require_logging(), 1);
54329
54171
  init_p58_config();
54330
54172
  var API_BASE_URL2 = "https://p58-n8n.path58.com";
@@ -54352,7 +54194,7 @@ async function callValidateEndpoint(token) {
54352
54194
  function writeConfigAtomic(cfg) {
54353
54195
  const configDir = (0, import_node_path2.dirname)(ACTIVATE_CONFIG_FILE);
54354
54196
  (0, import_node_fs2.mkdirSync)(configDir, { recursive: true });
54355
- const tmpFile = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `p58-config-${(0, import_node_crypto4.randomUUID)()}.json`);
54197
+ const tmpFile = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `p58-config-${(0, import_node_crypto2.randomUUID)()}.json`);
54356
54198
  (0, import_node_fs2.writeFileSync)(tmpFile, JSON.stringify(cfg, null, 2), { mode: 384 });
54357
54199
  (0, import_node_fs2.renameSync)(tmpFile, ACTIVATE_CONFIG_FILE);
54358
54200
  }