n8n-mcp 2.65.2 → 2.66.0

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 (58) hide show
  1. package/dist/mcp/handlers-n8n-manager.d.ts.map +1 -1
  2. package/dist/mcp/handlers-n8n-manager.js +38 -8
  3. package/dist/mcp/handlers-n8n-manager.js.map +1 -1
  4. package/dist/mcp/handlers-workflow-diff.d.ts.map +1 -1
  5. package/dist/mcp/handlers-workflow-diff.js +18 -3
  6. package/dist/mcp/handlers-workflow-diff.js.map +1 -1
  7. package/dist/mcp/tool-docs/workflow_management/n8n-create-workflow.d.ts.map +1 -1
  8. package/dist/mcp/tool-docs/workflow_management/n8n-create-workflow.js +2 -1
  9. package/dist/mcp/tool-docs/workflow_management/n8n-create-workflow.js.map +1 -1
  10. package/dist/mcp/tool-docs/workflow_management/n8n-get-workflow.d.ts.map +1 -1
  11. package/dist/mcp/tool-docs/workflow_management/n8n-get-workflow.js +5 -3
  12. package/dist/mcp/tool-docs/workflow_management/n8n-get-workflow.js.map +1 -1
  13. package/dist/mcp/tool-docs/workflow_management/n8n-update-full-workflow.d.ts.map +1 -1
  14. package/dist/mcp/tool-docs/workflow_management/n8n-update-full-workflow.js +1 -0
  15. package/dist/mcp/tool-docs/workflow_management/n8n-update-full-workflow.js.map +1 -1
  16. package/dist/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.d.ts.map +1 -1
  17. package/dist/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.js +11 -4
  18. package/dist/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.js.map +1 -1
  19. package/dist/mcp/tools-n8n-manager.d.ts.map +1 -1
  20. package/dist/mcp/tools-n8n-manager.js +29 -1
  21. package/dist/mcp/tools-n8n-manager.js.map +1 -1
  22. package/dist/services/n8n-api-client.d.ts +11 -2
  23. package/dist/services/n8n-api-client.d.ts.map +1 -1
  24. package/dist/services/n8n-api-client.js +114 -16
  25. package/dist/services/n8n-api-client.js.map +1 -1
  26. package/dist/services/n8n-validation.d.ts.map +1 -1
  27. package/dist/services/n8n-validation.js +2 -0
  28. package/dist/services/n8n-validation.js.map +1 -1
  29. package/dist/services/node-groups.d.ts +60 -0
  30. package/dist/services/node-groups.d.ts.map +1 -0
  31. package/dist/services/node-groups.js +237 -0
  32. package/dist/services/node-groups.js.map +1 -0
  33. package/dist/services/workflow-diff-engine.d.ts +6 -0
  34. package/dist/services/workflow-diff-engine.d.ts.map +1 -1
  35. package/dist/services/workflow-diff-engine.js +132 -4
  36. package/dist/services/workflow-diff-engine.js.map +1 -1
  37. package/dist/services/workflow-validator.d.ts +7 -0
  38. package/dist/services/workflow-validator.d.ts.map +1 -1
  39. package/dist/services/workflow-validator.js +20 -0
  40. package/dist/services/workflow-validator.js.map +1 -1
  41. package/dist/services/workflow-versioning-service.d.ts +2 -0
  42. package/dist/services/workflow-versioning-service.d.ts.map +1 -1
  43. package/dist/services/workflow-versioning-service.js +11 -3
  44. package/dist/services/workflow-versioning-service.js.map +1 -1
  45. package/dist/telemetry/intent-classifier.d.ts.map +1 -1
  46. package/dist/telemetry/intent-classifier.js +1 -0
  47. package/dist/telemetry/intent-classifier.js.map +1 -1
  48. package/dist/telemetry/mutation-tracker.d.ts.map +1 -1
  49. package/dist/telemetry/mutation-tracker.js +5 -0
  50. package/dist/telemetry/mutation-tracker.js.map +1 -1
  51. package/dist/types/n8n-api.d.ts +10 -0
  52. package/dist/types/n8n-api.d.ts.map +1 -1
  53. package/dist/types/n8n-api.js.map +1 -1
  54. package/dist/types/workflow-diff.d.ts +13 -2
  55. package/dist/types/workflow-diff.d.ts.map +1 -1
  56. package/dist/types/workflow-diff.js +1 -1
  57. package/dist/types/workflow-diff.js.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nodeGroupInputSchema = exports.GROUP_DESCRIPTION_MAX_LENGTH = void 0;
4
+ exports.parseNodeGroupsInput = parseNodeGroupsInput;
5
+ exports.toWorkflowNodeGroup = toWorkflowNodeGroup;
6
+ exports.nodeGroupsField = nodeGroupsField;
7
+ exports.sanitizeGroupsForApi = sanitizeGroupsForApi;
8
+ exports.repairNodeGroups = repairNodeGroups;
9
+ exports.checkNodeGroups = checkNodeGroups;
10
+ exports.dropRejectedGroup = dropRejectedGroup;
11
+ exports.classifyGroupError = classifyGroupError;
12
+ const zod_1 = require("zod");
13
+ const uuid_1 = require("uuid");
14
+ const mcp_input_normalizer_1 = require("../utils/mcp-input-normalizer");
15
+ exports.GROUP_DESCRIPTION_MAX_LENGTH = 155;
16
+ exports.nodeGroupInputSchema = zod_1.z.object({
17
+ id: zod_1.z.string().trim().min(1).optional(),
18
+ name: zod_1.z.string().trim().min(1),
19
+ nodeIds: zod_1.z.array(zod_1.z.string().trim().min(1)).min(1),
20
+ description: zod_1.z.string().trim().max(exports.GROUP_DESCRIPTION_MAX_LENGTH).optional()
21
+ });
22
+ function parseNodeGroupsInput(value) {
23
+ if (value === undefined || value === null)
24
+ return undefined;
25
+ const groups = zod_1.z.array(exports.nodeGroupInputSchema).parse((0, mcp_input_normalizer_1.normalizeMcpJsonValue)(value));
26
+ return groups.map(toWorkflowNodeGroup);
27
+ }
28
+ function toWorkflowNodeGroup(group) {
29
+ const normalized = {
30
+ id: group.id?.trim() || (0, uuid_1.v4)(),
31
+ name: group.name.trim(),
32
+ nodeIds: group.nodeIds
33
+ };
34
+ const description = typeof group.description === 'string' ? group.description.trim() : '';
35
+ if (description)
36
+ normalized.description = description;
37
+ return normalized;
38
+ }
39
+ function isGroupLike(value) {
40
+ if (typeof value !== 'object' || value === null)
41
+ return false;
42
+ const group = value;
43
+ return typeof group.id === 'string' && typeof group.name === 'string' && Array.isArray(group.nodeIds);
44
+ }
45
+ function groupLabel(group) {
46
+ return group.name?.trim() ? group.name : group.id;
47
+ }
48
+ function nodeGroupsField(groups) {
49
+ return Array.isArray(groups) && groups.length > 0 ? { nodeGroups: groups } : {};
50
+ }
51
+ function sanitizeGroupsForApi(groups, options) {
52
+ if (!Array.isArray(groups))
53
+ return [];
54
+ return groups.filter(isGroupLike).map(group => {
55
+ const sanitized = {
56
+ id: group.id,
57
+ name: group.name,
58
+ nodeIds: group.nodeIds.filter(id => typeof id === 'string')
59
+ };
60
+ if (options.includeDescription && typeof group.description === 'string') {
61
+ const description = group.description.trim();
62
+ if (description)
63
+ sanitized.description = description;
64
+ }
65
+ return sanitized;
66
+ });
67
+ }
68
+ function repairNodeGroups(workflow, options = {}) {
69
+ const groups = workflow.nodeGroups;
70
+ if (!Array.isArray(groups) || groups.length === 0) {
71
+ return { nodeGroups: groups, issues: [] };
72
+ }
73
+ const knownIds = new Set((workflow.nodes ?? []).map(node => node?.id).filter((id) => typeof id === 'string'));
74
+ const authored = options.authoredGroups ?? new Set();
75
+ const issues = [];
76
+ const repaired = [];
77
+ const errors = [];
78
+ let changed = false;
79
+ for (const group of groups) {
80
+ if (!isGroupLike(group)) {
81
+ changed = true;
82
+ issues.push({
83
+ code: 'group-malformed',
84
+ group: 'unknown',
85
+ message: 'A canvas group was dropped because it is missing an id, a name, or its member list.'
86
+ });
87
+ continue;
88
+ }
89
+ const label = groupLabel(group);
90
+ const keptIds = group.nodeIds.filter(id => typeof id === 'string' && knownIds.has(id));
91
+ const removedCount = group.nodeIds.length - keptIds.length;
92
+ if (removedCount > 0 && authored.has(group.name)) {
93
+ const missing = group.nodeIds.filter(id => !keptIds.includes(id));
94
+ errors.push(`Node group "${label}" references ${missing.length === 1 ? 'node' : 'nodes'} ${missing
95
+ .map(id => `"${id}"`)
96
+ .join(', ')} that ${missing.length === 1 ? 'is' : 'are'} not in the workflow.`);
97
+ repaired.push(group);
98
+ continue;
99
+ }
100
+ if (keptIds.length === 0) {
101
+ changed = true;
102
+ issues.push({
103
+ code: 'group-empty',
104
+ group: label,
105
+ message: `Node group "${label}" was removed because none of its nodes are left in the workflow.`
106
+ });
107
+ continue;
108
+ }
109
+ if (removedCount > 0) {
110
+ changed = true;
111
+ issues.push({
112
+ code: 'group-member-removed',
113
+ group: label,
114
+ message: `Node group "${label}" lost ${removedCount} member${removedCount === 1 ? '' : 's'} that no longer exist in the workflow; the group was kept with its remaining ${keptIds.length} node${keptIds.length === 1 ? '' : 's'}.`
115
+ });
116
+ repaired.push({ ...group, nodeIds: keptIds });
117
+ continue;
118
+ }
119
+ repaired.push(group);
120
+ }
121
+ return {
122
+ nodeGroups: changed ? repaired : groups,
123
+ issues,
124
+ errors: errors.length > 0 ? errors : undefined
125
+ };
126
+ }
127
+ function checkNodeGroups(workflow, options = {}) {
128
+ const groups = workflow.nodeGroups;
129
+ if (!Array.isArray(groups) || groups.length === 0)
130
+ return [];
131
+ const issues = [];
132
+ const nodesById = new Map();
133
+ for (const node of workflow.nodes ?? []) {
134
+ if (node?.id)
135
+ nodesById.set(node.id, node);
136
+ }
137
+ const seenNames = new Set();
138
+ const groupByNodeId = new Map();
139
+ for (const group of groups) {
140
+ if (!isGroupLike(group))
141
+ continue;
142
+ const label = groupLabel(group);
143
+ if (seenNames.has(group.name)) {
144
+ issues.push({
145
+ code: 'group-duplicate-name',
146
+ group: label,
147
+ message: `Two node groups are named "${group.name}"; n8n requires group names to be unique.`
148
+ });
149
+ }
150
+ seenNames.add(group.name);
151
+ if (group.nodeIds.length === 0) {
152
+ issues.push({
153
+ code: 'group-empty',
154
+ group: label,
155
+ message: `Node group "${label}" has no members; n8n rejects empty groups.`
156
+ });
157
+ }
158
+ for (const nodeId of group.nodeIds) {
159
+ const node = nodesById.get(nodeId);
160
+ if (!node) {
161
+ issues.push({
162
+ code: 'group-member-removed',
163
+ group: label,
164
+ message: `Node group "${label}" references node ID "${nodeId}", which is not in the workflow.`
165
+ });
166
+ continue;
167
+ }
168
+ const owner = groupByNodeId.get(nodeId);
169
+ if (owner) {
170
+ issues.push({
171
+ code: 'group-node-in-multiple-groups',
172
+ group: label,
173
+ message: `Node "${node.name}" is in both "${owner}" and "${label}"; a node can only belong to one group.`
174
+ });
175
+ }
176
+ else {
177
+ groupByNodeId.set(nodeId, label);
178
+ }
179
+ if (options.isTrigger?.(node)) {
180
+ issues.push({
181
+ code: 'group-contains-trigger',
182
+ group: label,
183
+ message: `Node group "${label}" contains trigger node "${node.name}"; n8n does not allow triggers inside a group.`
184
+ });
185
+ }
186
+ }
187
+ }
188
+ return issues;
189
+ }
190
+ function dropRejectedGroup(groups, target) {
191
+ const byId = target.groupId ? groups.findIndex(group => group.id === target.groupId) : -1;
192
+ const index = byId !== -1
193
+ ? byId
194
+ : target.groupName
195
+ ? groups.findIndex(group => group.name === target.groupName)
196
+ : -1;
197
+ if (index === -1)
198
+ return { groups, dropped: null };
199
+ return { groups: groups.filter((_, i) => i !== index), dropped: groups[index] };
200
+ }
201
+ function classifyGroupError(error) {
202
+ const apiError = error;
203
+ const message = typeof apiError?.message === 'string' ? apiError.message : '';
204
+ if (!apiError || apiError.statusCode !== 400) {
205
+ return { kind: 'unrelated', message };
206
+ }
207
+ let detailsText = '';
208
+ try {
209
+ detailsText = apiError.details === undefined ? '' : JSON.stringify(apiError.details);
210
+ }
211
+ catch {
212
+ detailsText = '';
213
+ }
214
+ const haystack = `${message} ${detailsText}`;
215
+ if (/must NOT have additional propert/i.test(haystack)) {
216
+ const nested = /request\/body\/([A-Za-z0-9_]+)/.exec(haystack);
217
+ if (nested) {
218
+ return nested[1] === 'nodeGroups'
219
+ ? { kind: 'schema-description', message }
220
+ : { kind: 'unrelated', message };
221
+ }
222
+ return { kind: 'schema-field', message };
223
+ }
224
+ const identified = /(?:node group|group)\s+"(.+?)"\s*\(([^)]+)\)/i.exec(haystack);
225
+ if (identified) {
226
+ return { kind: 'semantic', groupName: identified[1], groupId: identified[2], message };
227
+ }
228
+ const named = /(?:node group|group)\s+"([^"]+)"/i.exec(haystack);
229
+ if (named) {
230
+ return { kind: 'semantic', groupName: named[1], message };
231
+ }
232
+ if (/nodeGroups/.test(haystack)) {
233
+ return { kind: 'semantic', message };
234
+ }
235
+ return { kind: 'unrelated', message };
236
+ }
237
+ //# sourceMappingURL=node-groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-groups.js","sourceRoot":"","sources":["../../src/services/node-groups.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ export declare class WorkflowDiffEngine {
8
8
  private tagsToAdd;
9
9
  private tagsToRemove;
10
10
  private transferToProjectId;
11
+ private authoredGroupNames;
11
12
  applyDiff(workflow: Workflow, request: WorkflowDiffRequest): Promise<WorkflowDiffResult>;
12
13
  private validateOperation;
13
14
  private applyOperation;
@@ -33,6 +34,11 @@ export declare class WorkflowDiffEngine {
33
34
  private applyRewireConnection;
34
35
  private applyUpdateSettings;
35
36
  private applyUpdateName;
37
+ private validateSetNodeGroups;
38
+ private resolveGroupMembers;
39
+ private applySetNodeGroups;
40
+ private authoredGroupNamesOrUndefined;
41
+ private finalizeNodeGroups;
36
42
  private applyAddTag;
37
43
  private applyRemoveTag;
38
44
  private validateActivateWorkflow;
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAwBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAoC,MAAM,kBAAkB,CAAC;AAuH9E,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAEpC,OAAO,CAAC,mBAAmB,CAAqB;IAK1C,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAqL9B,OAAO,CAAC,iBAAiB;IA4CzB,OAAO,CAAC,cAAc;IA+DtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IA6D1B,OAAO,CAAC,sBAAsB;IAuE9B,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IA4DhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IAsDvB,OAAO,CAAC,mBAAmB;IAgE3B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IAkF7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,0BAA0B;IAmElC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,iBAAiB;CA0C1B"}
1
+ {"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAyBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAuD,MAAM,kBAAkB,CAAC;AAwHjG,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAEpC,OAAO,CAAC,mBAAmB,CAAqB;IAEhD,OAAO,CAAC,kBAAkB,CAAqB;IAKzC,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IA+L9B,OAAO,CAAC,iBAAiB;IA8CzB,OAAO,CAAC,cAAc;IAkEtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAmE1B,OAAO,CAAC,sBAAsB;IA8E9B,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IA4DhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IAsDvB,OAAO,CAAC,mBAAmB;IAgE3B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IAkF7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,qBAAqB;IA+E7B,OAAO,CAAC,mBAAmB;IA+B3B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,6BAA6B;IAWrC,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,0BAA0B;IAmElC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,iBAAiB;CA0C1B"}
@@ -4,6 +4,7 @@ exports.WorkflowDiffEngine = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const workflow_diff_1 = require("../types/workflow-diff");
6
6
  const logger_1 = require("../utils/logger");
7
+ const node_groups_1 = require("./node-groups");
7
8
  const node_sanitizer_1 = require("./node-sanitizer");
8
9
  const node_type_utils_1 = require("../utils/node-type-utils");
9
10
  const logger = new logger_1.Logger({ prefix: '[WorkflowDiffEngine]' });
@@ -82,6 +83,7 @@ class WorkflowDiffEngine {
82
83
  this.removedNodeNames = new Set();
83
84
  this.tagsToAdd = [];
84
85
  this.tagsToRemove = [];
86
+ this.authoredGroupNames = new Set();
85
87
  }
86
88
  async applyDiff(workflow, request) {
87
89
  try {
@@ -92,6 +94,7 @@ class WorkflowDiffEngine {
92
94
  this.tagsToAdd = [];
93
95
  this.tagsToRemove = [];
94
96
  this.transferToProjectId = undefined;
97
+ this.authoredGroupNames.clear();
95
98
  const workflowCopy = JSON.parse(JSON.stringify(workflow));
96
99
  const operationEntries = buildExecutionEntries(request.operations);
97
100
  const nodeOperationCount = request.operations.filter(workflow_diff_1.isNodeOperation).length;
@@ -126,6 +129,7 @@ class WorkflowDiffEngine {
126
129
  failedIndices.push(index);
127
130
  }
128
131
  }
132
+ this.finalizeNodeGroups(workflowCopy);
129
133
  if (request.validateOnly) {
130
134
  return {
131
135
  success: errors.length === 0,
@@ -136,7 +140,8 @@ class WorkflowDiffEngine {
136
140
  errors: errors.length > 0 ? errors : undefined,
137
141
  warnings: this.warnings.length > 0 ? this.warnings : undefined,
138
142
  applied: appliedIndices,
139
- failed: failedIndices
143
+ failed: failedIndices,
144
+ authoredGroupNames: this.authoredGroupNamesOrUndefined()
140
145
  };
141
146
  }
142
147
  const shouldActivate = workflowCopy._shouldActivate === true;
@@ -157,7 +162,8 @@ class WorkflowDiffEngine {
157
162
  shouldDeactivate: shouldDeactivate || undefined,
158
163
  tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
159
164
  tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
160
- transferToProjectId: this.transferToProjectId || undefined
165
+ transferToProjectId: this.transferToProjectId || undefined,
166
+ authoredGroupNames: this.authoredGroupNamesOrUndefined()
161
167
  };
162
168
  }
163
169
  else {
@@ -197,11 +203,14 @@ class WorkflowDiffEngine {
197
203
  });
198
204
  logger.debug(`Sanitized ${this.modifiedNodeIds.size} modified nodes`);
199
205
  }
206
+ this.finalizeNodeGroups(workflowCopy);
200
207
  if (request.validateOnly) {
201
208
  return {
202
209
  success: true,
203
210
  workflow: workflowCopy,
204
- message: 'Validation successful. Operations are valid but not applied.'
211
+ message: 'Validation successful. Operations are valid but not applied.',
212
+ warnings: this.warnings.length > 0 ? this.warnings : undefined,
213
+ authoredGroupNames: this.authoredGroupNamesOrUndefined()
205
214
  };
206
215
  }
207
216
  const operationsApplied = request.operations.length;
@@ -219,7 +228,8 @@ class WorkflowDiffEngine {
219
228
  shouldDeactivate: shouldDeactivate || undefined,
220
229
  tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
221
230
  tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
222
- transferToProjectId: this.transferToProjectId || undefined
231
+ transferToProjectId: this.transferToProjectId || undefined,
232
+ authoredGroupNames: this.authoredGroupNamesOrUndefined()
223
233
  };
224
234
  }
225
235
  }
@@ -260,6 +270,8 @@ class WorkflowDiffEngine {
260
270
  case 'addTag':
261
271
  case 'removeTag':
262
272
  return null;
273
+ case 'setNodeGroups':
274
+ return this.validateSetNodeGroups(workflow, operation);
263
275
  case 'transferWorkflow':
264
276
  return this.validateTransferWorkflow(workflow, operation);
265
277
  case 'activateWorkflow':
@@ -312,6 +324,9 @@ class WorkflowDiffEngine {
312
324
  case 'updateName':
313
325
  this.applyUpdateName(workflow, operation);
314
326
  break;
327
+ case 'setNodeGroups':
328
+ this.applySetNodeGroups(workflow, operation);
329
+ break;
315
330
  case 'addTag':
316
331
  this.applyAddTag(workflow, operation);
317
332
  break;
@@ -375,6 +390,9 @@ class WorkflowDiffEngine {
375
390
  if (!node) {
376
391
  return this.formatNodeNotFoundError(workflow, operation.nodeId || operation.nodeName || '', 'updateNode');
377
392
  }
393
+ if ('id' in operation.updates && operation.updates.id !== node.id) {
394
+ return `Cannot change the id of node "${node.name}": node IDs are immutable because canvas groups and pinned data reference them. Remove and re-add the node instead.`;
395
+ }
378
396
  if (operation.updates.name && operation.updates.name !== node.name) {
379
397
  const normalizedNewName = this.normalizeNodeName(operation.updates.name);
380
398
  const normalizedCurrentName = this.normalizeNodeName(node.name);
@@ -420,6 +438,9 @@ class WorkflowDiffEngine {
420
438
  if (pathSegments.some(k => DANGEROUS_PATH_KEYS.has(k))) {
421
439
  return `patchNodeField: fieldPath "${operation.fieldPath}" contains a forbidden key (__proto__, constructor, or prototype)`;
422
440
  }
441
+ if (pathSegments[0] === 'id') {
442
+ return `Cannot patch the id of a node: node IDs are immutable because canvas groups and pinned data reference them. Remove and re-add the node instead.`;
443
+ }
423
444
  if (!Array.isArray(operation.patches) || operation.patches.length === 0) {
424
445
  return `patchNodeField requires a non-empty "patches" array of {find, replace} objects`;
425
446
  }
@@ -904,6 +925,113 @@ class WorkflowDiffEngine {
904
925
  applyUpdateName(workflow, operation) {
905
926
  workflow.name = operation.name;
906
927
  }
928
+ validateSetNodeGroups(workflow, operation) {
929
+ const groups = operation.nodeGroups;
930
+ if (!Array.isArray(groups)) {
931
+ return `setNodeGroups requires a "nodeGroups" array. Pass every group you want to keep, or [] to ungroup everything. Example: {type: "setNodeGroups", nodeGroups: [{name: "Enrich lead", nodeNames: ["Fetch company", "Score lead"]}]}`;
932
+ }
933
+ const seenNames = new Set();
934
+ const claimedNodes = new Map();
935
+ for (const group of groups) {
936
+ if (!group || typeof group !== 'object') {
937
+ return `setNodeGroups: every entry must be an object like {name: "Enrich lead", nodeNames: ["Fetch company", "Score lead"]}`;
938
+ }
939
+ const name = typeof group.name === 'string' ? group.name.trim() : '';
940
+ if (!name) {
941
+ return `setNodeGroups: every group needs a non-empty "name"`;
942
+ }
943
+ if (seenNames.has(name)) {
944
+ return `setNodeGroups: duplicate group name "${name}". n8n requires group names to be unique.`;
945
+ }
946
+ seenNames.add(name);
947
+ if (group.id !== undefined && (typeof group.id !== 'string' || !group.id.trim())) {
948
+ return `setNodeGroups: group "${name}" has a non-string "id". Omit it to have one generated.`;
949
+ }
950
+ if (group.description !== undefined) {
951
+ if (typeof group.description !== 'string') {
952
+ return `setNodeGroups: group "${name}" has a non-string "description"`;
953
+ }
954
+ if (group.description.trim().length > node_groups_1.GROUP_DESCRIPTION_MAX_LENGTH) {
955
+ return `setNodeGroups: group "${name}" has a description of ${group.description.trim().length} characters; n8n allows at most ${node_groups_1.GROUP_DESCRIPTION_MAX_LENGTH}.`;
956
+ }
957
+ }
958
+ const hasNames = Array.isArray(group.nodeNames) && group.nodeNames.length > 0;
959
+ const hasIds = Array.isArray(group.nodeIds) && group.nodeIds.length > 0;
960
+ if (hasNames === hasIds) {
961
+ return hasNames
962
+ ? `setNodeGroups: group "${name}" sets both "nodeNames" and "nodeIds" — use one or the other`
963
+ : `setNodeGroups: group "${name}" needs members in "nodeNames" (or "nodeIds")`;
964
+ }
965
+ const members = hasIds ? group.nodeIds : group.nodeNames;
966
+ const badIndex = members.findIndex(member => typeof member !== 'string' || !member.trim());
967
+ if (badIndex !== -1) {
968
+ return `setNodeGroups: group "${name}" has a member that is not a node ${hasIds ? 'ID' : 'name'} (${JSON.stringify(members[badIndex])})`;
969
+ }
970
+ const resolvedMembers = this.resolveGroupMembers(workflow, group);
971
+ if (typeof resolvedMembers === 'string')
972
+ return resolvedMembers;
973
+ for (const node of resolvedMembers) {
974
+ const owner = claimedNodes.get(node.id);
975
+ if (owner && owner !== name) {
976
+ return `setNodeGroups: node "${node.name}" is in both "${owner}" and "${name}" — a node can only belong to one group`;
977
+ }
978
+ claimedNodes.set(node.id, name);
979
+ }
980
+ }
981
+ return null;
982
+ }
983
+ resolveGroupMembers(workflow, group) {
984
+ const label = group.name?.trim() || group.id || 'unnamed group';
985
+ const resolved = [];
986
+ if (Array.isArray(group.nodeIds) && group.nodeIds.length > 0) {
987
+ for (const nodeId of group.nodeIds) {
988
+ const node = workflow.nodes.find(n => n.id === nodeId);
989
+ if (!node) {
990
+ return `setNodeGroups: group "${label}" references node ID "${nodeId}", which is not in the workflow. ${this.formatNodeNotFoundError(workflow, nodeId, 'setNodeGroups')}`;
991
+ }
992
+ resolved.push(node);
993
+ }
994
+ return resolved;
995
+ }
996
+ for (const nodeName of group.nodeNames ?? []) {
997
+ const normalized = this.normalizeNodeName(nodeName);
998
+ const node = workflow.nodes.find(n => this.normalizeNodeName(n.name) === normalized);
999
+ if (!node) {
1000
+ return `setNodeGroups: group "${label}" references node "${nodeName}", which is not in the workflow. ${this.formatNodeNotFoundError(workflow, nodeName, 'setNodeGroups')}`;
1001
+ }
1002
+ resolved.push(node);
1003
+ }
1004
+ return resolved;
1005
+ }
1006
+ applySetNodeGroups(workflow, operation) {
1007
+ const groups = [];
1008
+ for (const group of operation.nodeGroups) {
1009
+ const members = this.resolveGroupMembers(workflow, group);
1010
+ if (typeof members === 'string')
1011
+ throw new Error(members);
1012
+ const resolved = (0, node_groups_1.toWorkflowNodeGroup)({
1013
+ id: group.id,
1014
+ name: group.name,
1015
+ nodeIds: [...new Set(members.map(node => node.id))],
1016
+ description: group.description
1017
+ });
1018
+ groups.push(resolved);
1019
+ this.authoredGroupNames.add(resolved.name);
1020
+ }
1021
+ workflow.nodeGroups = groups;
1022
+ }
1023
+ authoredGroupNamesOrUndefined() {
1024
+ return this.authoredGroupNames.size > 0 ? [...this.authoredGroupNames] : undefined;
1025
+ }
1026
+ finalizeNodeGroups(workflow) {
1027
+ if (!Array.isArray(workflow.nodeGroups) || workflow.nodeGroups.length === 0)
1028
+ return;
1029
+ const { nodeGroups, issues } = (0, node_groups_1.repairNodeGroups)(workflow);
1030
+ workflow.nodeGroups = nodeGroups;
1031
+ for (const issue of issues) {
1032
+ this.warnings.push({ operation: -1, message: issue.message });
1033
+ }
1034
+ }
907
1035
  applyAddTag(workflow, operation) {
908
1036
  const removeIdx = this.tagsToRemove.indexOf(operation.tag);
909
1037
  if (removeIdx !== -1) {