@learningsuite/n8n-nodes-learningsuite 1.2.5 → 1.3.4

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 (33) hide show
  1. package/README.md +41 -12
  2. package/dist/nodes/LearningSuite/LearningSuite.node.d.ts +7 -1
  3. package/dist/nodes/LearningSuite/LearningSuite.node.js +2 -4
  4. package/dist/nodes/LearningSuite/LearningSuiteTrigger.node.d.ts +7 -1
  5. package/dist/nodes/LearningSuite/LearningSuiteTrigger.node.js +15 -1
  6. package/dist/nodes/LearningSuite/descriptions/ai.properties.js +86 -0
  7. package/dist/nodes/LearningSuite/descriptions/customFields.properties.js +138 -17
  8. package/dist/nodes/LearningSuite/descriptions/member.properties.js +1 -1
  9. package/dist/nodes/LearningSuite/descriptions/module.properties.js +1 -1
  10. package/dist/nodes/LearningSuite/descriptions/trigger.instant.properties.js +35 -3
  11. package/dist/nodes/LearningSuite/descriptions/webhook.properties.js +37 -2
  12. package/dist/nodes/LearningSuite/descriptions/webhook.sampleData.properties.js +26 -0
  13. package/dist/nodes/LearningSuite/exec.types.d.ts +2 -2
  14. package/dist/nodes/LearningSuite/execute/ai.handlers.d.ts +3 -0
  15. package/dist/nodes/LearningSuite/execute/ai.handlers.js +44 -0
  16. package/dist/nodes/LearningSuite/execute/customFields.handlers.d.ts +1 -0
  17. package/dist/nodes/LearningSuite/execute/customFields.handlers.js +273 -22
  18. package/dist/nodes/LearningSuite/execute/user.handlers.js +1 -1
  19. package/dist/nodes/LearningSuite/execute/webhook.handlers.js +17 -1
  20. package/dist/nodes/LearningSuite/methods/loadOptions/ai.loadOptions.d.ts +3 -0
  21. package/dist/nodes/LearningSuite/methods/loadOptions/ai.loadOptions.js +11 -0
  22. package/dist/nodes/LearningSuite/methods/loadOptions/common.d.ts +1 -0
  23. package/dist/nodes/LearningSuite/methods/loadOptions/common.js +5 -0
  24. package/dist/nodes/LearningSuite/methods/loadOptions/customFields.loadOptions.d.ts +8 -4
  25. package/dist/nodes/LearningSuite/methods/loadOptions/customFields.loadOptions.js +76 -3
  26. package/dist/nodes/LearningSuite/methods/loadOptions/teamMember.loadOptions.js +1 -1
  27. package/dist/nodes/LearningSuite/shared/customFields.helpers.js +1 -4
  28. package/dist/nodes/LearningSuite/shared/customFields.shared.d.ts +1 -0
  29. package/dist/nodes/LearningSuite/shared/customFields.upload.d.ts +1 -1
  30. package/dist/nodes/LearningSuite/shared/customFields.upload.js +11 -10
  31. package/dist/nodes/LearningSuite/shared/request.d.ts +3 -0
  32. package/dist/nodes/LearningSuite/shared/request.js +18 -0
  33. package/package.json +1 -1
@@ -80,6 +80,7 @@ exports.webhookProperties = [
80
80
  displayOptions: { show: { resource: ['webhook'], operation: ['createSubscription', 'updateSubscription'] } },
81
81
  description: 'Type of event to subscribe to',
82
82
  options: [
83
+ { name: 'Agent Action Executed', value: 'agentAction.executed' },
83
84
  { name: 'Community Post Commented', value: 'communityPost.commented' },
84
85
  { name: 'Community Post Created', value: 'communityPost.created' },
85
86
  { name: 'Community Post Moderated', value: 'communityPost.moderated' },
@@ -109,6 +110,7 @@ exports.webhookProperties = [
109
110
  required: true,
110
111
  description: 'Type of sample data to retrieve',
111
112
  options: [
113
+ { name: 'Agent Action Executed Events', value: 'agent-action-executed-events' },
112
114
  { name: 'Community Post Commented', value: 'community-post-commented-events' },
113
115
  { name: 'Community Post Created Events', value: 'community-post-created-events' },
114
116
  { name: 'Community Post Moderated Events', value: 'community-post-moderated-events' },
@@ -126,7 +128,40 @@ exports.webhookProperties = [
126
128
  { name: 'User Activation Status Changed Events', value: 'user-activation-status-changed-events' },
127
129
  ],
128
130
  },
129
- // ====== Optionen pro Event (identisch zu instantProperties) ======
131
+ // ====== Options per event (identical to instantProperties) ======
132
+ // Agent Action Executed
133
+ {
134
+ displayName: 'Agent Action Executed Options',
135
+ name: 'additionalAgentActionExecuted',
136
+ type: 'collection',
137
+ default: {},
138
+ placeholder: 'Add option',
139
+ displayOptions: {
140
+ show: {
141
+ resource: ['webhook'],
142
+ operation: ['createSubscription', 'updateSubscription'],
143
+ eventType: ['agentAction.executed'],
144
+ },
145
+ },
146
+ options: [
147
+ {
148
+ displayName: 'Tool Key Name or ID',
149
+ name: 'toolKey',
150
+ type: 'options',
151
+ default: '',
152
+ description: 'Optional: Only trigger for a specific agent action. Leave empty to trigger for all actions. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
153
+ typeOptions: { loadOptionsMethod: 'ai_getAgentActions' },
154
+ },
155
+ {
156
+ displayName: 'Agent Name or ID',
157
+ name: 'agentId',
158
+ type: 'options',
159
+ default: '',
160
+ description: 'Optional: Only trigger for actions executed by a specific AI agent. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
161
+ typeOptions: { loadOptionsMethod: 'ai_getAiAgents' },
162
+ },
163
+ ],
164
+ },
130
165
  // Login Options
131
166
  {
132
167
  displayName: 'Login Options',
@@ -532,7 +567,7 @@ exports.webhookProperties = [
532
567
  },
533
568
  ],
534
569
  },
535
- // Lesson Completed (kaskadiert)
570
+ // Lesson Completed (cascading)
536
571
  {
537
572
  displayName: 'Lesson Completed Options',
538
573
  name: 'additionalLessonCompleted',
@@ -3,6 +3,32 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.webhookSampleDataProperties = void 0;
5
5
  exports.webhookSampleDataProperties = [
6
+ {
7
+ displayName: 'Agent Action Executed Options',
8
+ name: 'additionalAgentActionExecutedSample',
9
+ type: 'collection',
10
+ default: {},
11
+ placeholder: 'Add option',
12
+ displayOptions: { show: { sampleDataType: ['agent-action-executed-events'] } },
13
+ options: [
14
+ {
15
+ displayName: 'Tool Key Name or ID',
16
+ name: 'toolKey',
17
+ type: 'options',
18
+ default: '',
19
+ description: 'Optional tool key to filter agent action executions. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
20
+ typeOptions: { loadOptionsMethod: 'ai_getAgentActions' },
21
+ },
22
+ {
23
+ displayName: 'Agent Name or ID',
24
+ name: 'agentId',
25
+ type: 'options',
26
+ default: '',
27
+ description: 'Optional agent ID to filter agent action executions. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
28
+ typeOptions: { loadOptionsMethod: 'ai_getAiAgents' },
29
+ },
30
+ ],
31
+ },
6
32
  {
7
33
  displayName: 'Progress Changed Options',
8
34
  name: 'additionalProgressChangedSample',
@@ -1,5 +1,5 @@
1
1
  import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2
- /** Einheitlicher Funktions-Typ für alle Execute-Handler. */
2
+ /** Unified function type for all execute handlers. */
3
3
  export type ExecuteHandler = (ctx: IExecuteFunctions, itemIndex: number) => Promise<IDataObject | IDataObject[]>;
4
- /** Registry-Typ: resource -> operation -> handler */
4
+ /** Registry type: resource -> operation -> handler */
5
5
  export type HandlersRegistry = Record<string, Record<string, ExecuteHandler>>;
@@ -1,4 +1,7 @@
1
1
  import type { ExecuteHandler } from '../exec.types';
2
2
  export declare const aiHandlers: {
3
+ agentChat: ExecuteHandler;
4
+ getAgentActions: ExecuteHandler;
5
+ getAiAgents: ExecuteHandler;
3
6
  ragChat: ExecuteHandler;
4
7
  };
@@ -1,7 +1,48 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.aiHandlers = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
4
5
  const shared_1 = require("../shared");
6
+ const getAgentActions = async (ctx) => {
7
+ return await shared_1.lsRequest.call(ctx, 'GET', '/agent-actions');
8
+ };
9
+ const getAiAgents = async (ctx) => {
10
+ return await shared_1.lsRequest.call(ctx, 'GET', '/ai-agents');
11
+ };
12
+ function parseJsonObjectParam(ctx, i, name) {
13
+ const raw = ctx.getNodeParameter(name, i, '');
14
+ if (raw === undefined || raw === null || raw === '')
15
+ return undefined;
16
+ if (typeof raw === 'object')
17
+ return raw;
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(raw);
21
+ }
22
+ catch (err) {
23
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Invalid JSON in "${name}": ${String(err)}`);
24
+ }
25
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
26
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `"${name}" must be a JSON object`);
27
+ }
28
+ return parsed;
29
+ }
30
+ const agentChat = async (ctx, i) => {
31
+ const agentId = ctx.getNodeParameter('agentId', i);
32
+ if (!agentId)
33
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'AI Agent is required');
34
+ const body = {
35
+ userId: ctx.getNodeParameter('userId', i),
36
+ chatId: ctx.getNodeParameter('chatId', i, undefined),
37
+ message: ctx.getNodeParameter('message', i, undefined),
38
+ includeChatHistory: ctx.getNodeParameter('includeChatHistory', i, false),
39
+ endChat: ctx.getNodeParameter('endChat', i, false),
40
+ metadata: parseJsonObjectParam(ctx, i, 'metadata'),
41
+ profileContext: parseJsonObjectParam(ctx, i, 'profileContext'),
42
+ };
43
+ const cleanBody = Object.fromEntries(Object.entries(body).filter(([, v]) => v !== undefined && v !== ''));
44
+ return await shared_1.lsRequest.call(ctx, 'POST', `/ai-agents/${encodeURIComponent(agentId)}/chat`, { body: cleanBody });
45
+ };
5
46
  const ragChat = async (ctx, i) => {
6
47
  const body = {
7
48
  question: ctx.getNodeParameter('question', i),
@@ -19,5 +60,8 @@ const ragChat = async (ctx, i) => {
19
60
  return await shared_1.lsRequest.call(ctx, 'POST', '/ai/rag-chat', { body: cleanBody });
20
61
  };
21
62
  exports.aiHandlers = {
63
+ agentChat,
64
+ getAgentActions,
65
+ getAiAgents,
22
66
  ragChat,
23
67
  };
@@ -9,6 +9,7 @@ export declare const customFieldsHandlers: {
9
9
  getFieldValues: ExecuteHandler;
10
10
  setFieldValue: ExecuteHandler;
11
11
  setMultipleFieldValues: ExecuteHandler;
12
+ createFileUploadTarget: ExecuteHandler;
12
13
  getProfiles: ExecuteHandler;
13
14
  getProfilesExpanded: ExecuteHandler;
14
15
  getProfileByCard: ExecuteHandler;
@@ -95,6 +95,178 @@ function normalizeCustomFieldValue(response) {
95
95
  return { value: response[0] };
96
96
  return { value: response };
97
97
  }
98
+ function normalizeValuesArray(value) {
99
+ if (value === undefined || value === null) {
100
+ return [];
101
+ }
102
+ return Array.isArray(value) ? value : [value];
103
+ }
104
+ function normalizeStoreFileValuesResponse(response) {
105
+ if (!Array.isArray(response)) {
106
+ return normalizeValuesArray(response);
107
+ }
108
+ if (response.length === 0) {
109
+ return [];
110
+ }
111
+ if (response.length === 1 && Array.isArray(response[0])) {
112
+ return response[0];
113
+ }
114
+ return response;
115
+ }
116
+ function normalizeFileValuesResponse(response) {
117
+ return normalizeStoreFileValuesResponse(response);
118
+ }
119
+ function normalizeProfileIndex(value) {
120
+ if (typeof value === 'number' && Number.isFinite(value)) {
121
+ return value;
122
+ }
123
+ if (typeof value === 'string' && value.trim() !== '') {
124
+ const parsed = Number(value);
125
+ return Number.isFinite(parsed) ? parsed : undefined;
126
+ }
127
+ return undefined;
128
+ }
129
+ function getMediaFieldLimit(type, typeDefinition) {
130
+ switch (type) {
131
+ case 'files':
132
+ return typeDefinition.maxFiles;
133
+ case 'images':
134
+ return typeDefinition.maxImages;
135
+ case 'videos':
136
+ return typeDefinition.maxVideos;
137
+ case 'audios':
138
+ return typeDefinition.maxAudios;
139
+ default:
140
+ return undefined;
141
+ }
142
+ }
143
+ function buildProfileSelector(ctx, i) {
144
+ const profileId = ctx.getNodeParameter('profileId', i, '');
145
+ const profileIndex = normalizeProfileIndex(ctx.getNodeParameter('profileIndex', i, ''));
146
+ const profileName = ctx.getNodeParameter('profileName', i, '');
147
+ const selector = {};
148
+ if (profileId) {
149
+ selector.profileId = profileId;
150
+ }
151
+ else if (profileIndex !== undefined) {
152
+ selector.profileIndex = profileIndex;
153
+ }
154
+ else if (profileName) {
155
+ selector.profileName = profileName;
156
+ }
157
+ return selector;
158
+ }
159
+ function hasProfileSelector(selector) {
160
+ return selector.profileId !== undefined || selector.profileIndex !== undefined || selector.profileName !== undefined;
161
+ }
162
+ function getEffectiveProfileSelector(fieldContext, selector) {
163
+ return fieldContext.multipleProfilesAllowed ? selector : {};
164
+ }
165
+ async function cardAllowsMultipleProfiles(ctx, cardId) {
166
+ const cards = await shared_1.lsRequest.call(ctx, 'GET', '/custom-fields/cards');
167
+ if (!Array.isArray(cards)) {
168
+ return false;
169
+ }
170
+ const card = cards.find((entry) => {
171
+ if (typeof entry !== 'object' || entry === null)
172
+ return false;
173
+ return entry.id === cardId;
174
+ });
175
+ if (!card || typeof card !== 'object') {
176
+ return false;
177
+ }
178
+ return card.multipleProfilesAllowed === true;
179
+ }
180
+ function getFileValueMode(ctx, i) {
181
+ return ctx.getNodeParameter('fileValueMode', i, 'add');
182
+ }
183
+ function countBinaryPropertyNames(binaryPropertyNames) {
184
+ return binaryPropertyNames
185
+ .split(',')
186
+ .map((name) => name.trim())
187
+ .filter((name) => name.length > 0).length;
188
+ }
189
+ function findFileFieldContext(ctx, cards, customFieldKey) {
190
+ var _a, _b, _c;
191
+ for (const card of cards) {
192
+ if (!(card === null || card === void 0 ? void 0 : card.id) || !Array.isArray(card.definitions)) {
193
+ continue;
194
+ }
195
+ const definition = card.definitions.find((def) => (def === null || def === void 0 ? void 0 : def.key) === customFieldKey);
196
+ if (!definition) {
197
+ continue;
198
+ }
199
+ const type = String((_b = (_a = definition.typeDefinition) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : '');
200
+ if (!FILE_FIELD_TYPES.has(type)) {
201
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Custom field "${customFieldKey}" is not a file, image, video, or audio field.`);
202
+ }
203
+ return {
204
+ cardId: card.id,
205
+ type,
206
+ maxItems: getMediaFieldLimit(type, (_c = definition.typeDefinition) !== null && _c !== void 0 ? _c : {}),
207
+ multipleProfilesAllowed: card.multipleProfilesAllowed === true,
208
+ };
209
+ }
210
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Unknown custom field "${customFieldKey}".`);
211
+ }
212
+ async function getPublicUrlFileFieldContext(ctx, customFieldKey) {
213
+ const cardsResponse = await shared_1.lsRequest.call(ctx, 'GET', '/custom-fields/cards/expanded');
214
+ if (!Array.isArray(cardsResponse)) {
215
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Failed to load custom field definitions.');
216
+ }
217
+ return findFileFieldContext(ctx, cardsResponse, customFieldKey);
218
+ }
219
+ async function readExistingFileValues(ctx, userId, customFieldKey, fieldContext, profileSelector, forceProfileContext = false) {
220
+ const effectiveProfileSelector = getEffectiveProfileSelector(fieldContext, profileSelector);
221
+ const useProfileContext = forceProfileContext || fieldContext.multipleProfilesAllowed || hasProfileSelector(effectiveProfileSelector);
222
+ if (useProfileContext) {
223
+ const existingProfileValues = await shared_1.lsRequest.call(ctx, 'GET', `/custom-fields/store/${userId}/profiles/by-card/${fieldContext.cardId}`, { qs: effectiveProfileSelector });
224
+ return {
225
+ existingValues: normalizeFileValuesResponse(existingProfileValues === null || existingProfileValues === void 0 ? void 0 : existingProfileValues[customFieldKey]),
226
+ useProfileContext,
227
+ };
228
+ }
229
+ const existingFieldValues = await shared_1.lsRequest.call(ctx, 'GET', `/custom-fields/store/${userId}/fields/${customFieldKey}`);
230
+ return {
231
+ existingValues: normalizeStoreFileValuesResponse(existingFieldValues),
232
+ useProfileContext,
233
+ };
234
+ }
235
+ function assertFileValueCountWithinLimit(ctx, customFieldKey, valueCount, maxItems) {
236
+ if (maxItems !== undefined && valueCount > maxItems) {
237
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Cannot set custom field "${customFieldKey}". Maximum of ${maxItems} file values would be exceeded.`);
238
+ }
239
+ }
240
+ function buildNextFileValues(ctx, customFieldKey, existingValues, uploadedValues, maxItems, mode) {
241
+ assertFileValueCountWithinLimit(ctx, customFieldKey, uploadedValues.length, maxItems);
242
+ if (mode === 'replace') {
243
+ return uploadedValues;
244
+ }
245
+ const appendedValues = [...existingValues, ...uploadedValues];
246
+ if (maxItems === undefined || appendedValues.length <= maxItems) {
247
+ return appendedValues;
248
+ }
249
+ if (mode === 'replaceIfFull') {
250
+ return uploadedValues;
251
+ }
252
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Cannot add file to custom field "${customFieldKey}". Maximum of ${maxItems} would be exceeded.`);
253
+ }
254
+ async function writeFileValues(ctx, userId, customFieldKey, fieldContext, profileSelector, useProfileContext, nextValues) {
255
+ const effectiveProfileSelector = getEffectiveProfileSelector(fieldContext, profileSelector);
256
+ if (useProfileContext) {
257
+ const updateBody = {
258
+ fieldKey: customFieldKey,
259
+ fieldValue: nextValues,
260
+ ...effectiveProfileSelector,
261
+ };
262
+ return await shared_1.lsRequest.call(ctx, 'PUT', `/custom-fields/store/${userId}/profiles/by-card/${fieldContext.cardId}`, {
263
+ body: updateBody,
264
+ });
265
+ }
266
+ return await shared_1.lsRequest.call(ctx, 'PUT', `/custom-fields/store/${userId}/fields/${customFieldKey}`, {
267
+ body: { fieldValue: nextValues },
268
+ });
269
+ }
98
270
  function readTypedFieldValue(ctx, i, fieldType) {
99
271
  switch (fieldType) {
100
272
  case 'string':
@@ -154,9 +326,9 @@ const getStore = async (ctx, i) => {
154
326
  };
155
327
  const getStoreValues = async (ctx, i) => {
156
328
  const userId = ctx.getNodeParameter('userId', i);
157
- const profileIndex = ctx.getNodeParameter('profileIndex', i, null);
329
+ const profileIndex = normalizeProfileIndex(ctx.getNodeParameter('profileIndex', i, ''));
158
330
  const qs = {};
159
- if (profileIndex !== null && Number.isFinite(profileIndex)) {
331
+ if (profileIndex !== undefined) {
160
332
  qs.profileIndex = profileIndex;
161
333
  }
162
334
  return await shared_1.lsRequest.call(ctx, 'GET', `/custom-fields/store/${userId}/values`, { qs });
@@ -172,10 +344,24 @@ const setFieldValue = async (ctx, i) => {
172
344
  const fieldKey = ctx.getNodeParameter('fieldKey', i);
173
345
  const fieldType = ctx.getNodeParameter('fieldType', i, '');
174
346
  let fieldValue;
347
+ let fileFieldContext;
348
+ let fileProfileSelector;
349
+ let useFileProfileContext = false;
175
350
  if (FILE_FIELD_TYPES.has(fieldType)) {
176
351
  const binaryPropertyNames = readTypedFieldValue(ctx, i, fieldType);
177
352
  const fileNameOverride = ctx.getNodeParameter('fieldValueFileName', i, '').trim() || undefined;
178
- fieldValue = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, binaryPropertyNames, fieldType, fileNameOverride);
353
+ const fileValueMode = getFileValueMode(ctx, i);
354
+ fileFieldContext = await getPublicUrlFileFieldContext(ctx, fieldKey);
355
+ fileProfileSelector = buildProfileSelector(ctx, i);
356
+ const { existingValues, useProfileContext } = await readExistingFileValues(ctx, userId, fieldKey, fileFieldContext, fileProfileSelector);
357
+ useFileProfileContext = useProfileContext;
358
+ const uploadCount = countBinaryPropertyNames(binaryPropertyNames);
359
+ if (fileValueMode !== 'replaceIfFull') {
360
+ const expectedValues = fileValueMode === 'replace' ? uploadCount : existingValues.length + uploadCount;
361
+ assertFileValueCountWithinLimit(ctx, fieldKey, expectedValues, fileFieldContext.maxItems);
362
+ }
363
+ const uploadedValues = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, fieldKey, binaryPropertyNames, fieldType, fileNameOverride);
364
+ fieldValue = buildNextFileValues(ctx, fieldKey, existingValues, uploadedValues, fileFieldContext.maxItems, fileValueMode);
179
365
  }
180
366
  else {
181
367
  fieldValue = readTypedFieldValue(ctx, i, fieldType);
@@ -184,8 +370,7 @@ const setFieldValue = async (ctx, i) => {
184
370
  throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `No value provided for custom field "${fieldKey}".`);
185
371
  }
186
372
  const profileId = ctx.getNodeParameter('profileId', i, '');
187
- const profileIndex = ctx.getNodeParameter('profileIndex', i, null);
188
- const validProfileIndex = profileIndex !== null && Number.isFinite(profileIndex) ? profileIndex : undefined;
373
+ const validProfileIndex = normalizeProfileIndex(ctx.getNodeParameter('profileIndex', i, ''));
189
374
  const body = { fieldValue };
190
375
  if (profileId) {
191
376
  body.profileId = profileId;
@@ -193,6 +378,10 @@ const setFieldValue = async (ctx, i) => {
193
378
  else if (validProfileIndex !== undefined) {
194
379
  body.profileIndex = validProfileIndex;
195
380
  }
381
+ if (fileFieldContext && fileProfileSelector) {
382
+ const response = await writeFileValues(ctx, userId, fieldKey, fileFieldContext, fileProfileSelector, useFileProfileContext, fieldValue);
383
+ return normalizeCustomFieldValue(response);
384
+ }
196
385
  const response = await shared_1.lsRequest.call(ctx, 'PUT', `/custom-fields/store/${userId}/fields/${fieldKey}`, { body });
197
386
  return normalizeCustomFieldValue(response);
198
387
  };
@@ -213,8 +402,8 @@ function buildFieldTypeMap(cards) {
213
402
  const setMultipleFieldValues = async (ctx, i) => {
214
403
  const userId = ctx.getNodeParameter('userId', i);
215
404
  const profileId = ctx.getNodeParameter('profileId', i, '');
216
- const profileIndex = ctx.getNodeParameter('profileIndex', i, null);
217
- const validProfileIndex = profileIndex !== null && Number.isFinite(profileIndex) ? profileIndex : undefined;
405
+ const validProfileIndex = normalizeProfileIndex(ctx.getNodeParameter('profileIndex', i, ''));
406
+ const fileValueMode = getFileValueMode(ctx, i);
218
407
  const mapperData = ctx.getNodeParameter('fieldValues', i);
219
408
  const fieldMappings = (mapperData === null || mapperData === void 0 ? void 0 : mapperData.value) || {};
220
409
  const cardsResponse = await shared_1.lsRequest.call(ctx, 'GET', '/custom-fields/cards/expanded');
@@ -222,6 +411,7 @@ const setMultipleFieldValues = async (ctx, i) => {
222
411
  throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Failed to load custom field definitions.');
223
412
  }
224
413
  const cards = cardsResponse;
414
+ const expandedCards = cardsResponse;
225
415
  const fieldTypeMap = buildFieldTypeMap(cards);
226
416
  const entries = Object.entries(fieldMappings).filter(([, value]) => value !== undefined);
227
417
  const payload = [];
@@ -233,7 +423,22 @@ const setMultipleFieldValues = async (ctx, i) => {
233
423
  let fieldValue;
234
424
  if (FILE_FIELD_TYPES.has(lsType)) {
235
425
  const binaryPropertyNames = typeof value === 'string' ? value : 'data';
236
- fieldValue = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, binaryPropertyNames, lsType);
426
+ const fieldContext = findFileFieldContext(ctx, expandedCards, fieldKey);
427
+ const profileSelector = {};
428
+ if (profileId) {
429
+ profileSelector.profileId = profileId;
430
+ }
431
+ else if (validProfileIndex !== undefined) {
432
+ profileSelector.profileIndex = validProfileIndex;
433
+ }
434
+ const { existingValues } = await readExistingFileValues(ctx, userId, fieldKey, fieldContext, profileSelector);
435
+ const uploadCount = countBinaryPropertyNames(binaryPropertyNames);
436
+ if (fileValueMode !== 'replaceIfFull') {
437
+ const expectedValues = fileValueMode === 'replace' ? uploadCount : existingValues.length + uploadCount;
438
+ assertFileValueCountWithinLimit(ctx, fieldKey, expectedValues, fieldContext.maxItems);
439
+ }
440
+ const uploadedValues = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, fieldKey, binaryPropertyNames, lsType);
441
+ fieldValue = buildNextFileValues(ctx, fieldKey, existingValues, uploadedValues, fieldContext.maxItems, fileValueMode);
237
442
  }
238
443
  else {
239
444
  fieldValue = normalizeSingleFieldValueOrFail(ctx, value, fieldKey, lsType);
@@ -254,6 +459,37 @@ const setMultipleFieldValues = async (ctx, i) => {
254
459
  body: payload,
255
460
  });
256
461
  };
462
+ const createFileUploadTarget = async (ctx, i) => {
463
+ const userId = ctx.getNodeParameter('userId', i);
464
+ const customFieldKey = (ctx.getNodeParameter('customFieldKey', i, '') || ctx.getNodeParameter('fieldKey', i, '')).trim();
465
+ const fileName = ctx.getNodeParameter('customFieldFileName', i, '').trim();
466
+ const publicDownloadUrl = ctx.getNodeParameter('publicDownloadUrl', i, '').trim();
467
+ const profileSelector = buildProfileSelector(ctx, i);
468
+ const fileValueMode = getFileValueMode(ctx, i);
469
+ if (!customFieldKey) {
470
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'No custom field key provided.');
471
+ }
472
+ if (!publicDownloadUrl) {
473
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'No public download URL provided.');
474
+ }
475
+ const fieldContext = await getPublicUrlFileFieldContext(ctx, customFieldKey);
476
+ const { existingValues, useProfileContext } = await readExistingFileValues(ctx, userId, customFieldKey, fieldContext, profileSelector);
477
+ const body = { customFieldKey };
478
+ if (fileName) {
479
+ body.fileName = fileName;
480
+ }
481
+ body.publicDownloadUrl = publicDownloadUrl;
482
+ const createFileResponse = (await shared_1.lsRequest.call(ctx, 'POST', `/custom-fields/store/${userId}/files`, {
483
+ body,
484
+ }));
485
+ const customFieldValue = createFileResponse.customFieldValue;
486
+ if (customFieldValue === undefined || customFieldValue === null) {
487
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'LearningSuite did not return a custom field value for the uploaded file.');
488
+ }
489
+ const uploadedValues = normalizeValuesArray(customFieldValue);
490
+ const nextValues = buildNextFileValues(ctx, customFieldKey, existingValues, uploadedValues, fieldContext.maxItems, fileValueMode);
491
+ return await writeFileValues(ctx, userId, customFieldKey, fieldContext, profileSelector, useProfileContext, nextValues);
492
+ };
257
493
  const getProfiles = async (ctx, i) => {
258
494
  const userId = ctx.getNodeParameter('userId', i);
259
495
  const customFieldCardId = ctx.getNodeParameter('customFieldCardId', i, undefined);
@@ -274,13 +510,13 @@ const getProfileByCard = async (ctx, i) => {
274
510
  const userId = ctx.getNodeParameter('userId', i);
275
511
  const cardId = ctx.getNodeParameter('customFieldCardId', i);
276
512
  const profileId = ctx.getNodeParameter('profileId', i, '');
277
- const profileIndex = ctx.getNodeParameter('profileIndex', i, null);
513
+ const profileIndex = normalizeProfileIndex(ctx.getNodeParameter('profileIndex', i, ''));
278
514
  const profileName = ctx.getNodeParameter('profileName', i, '');
279
515
  const qs = {};
280
516
  if (profileId) {
281
517
  qs.profileId = profileId;
282
518
  }
283
- else if (profileIndex !== null && Number.isFinite(profileIndex)) {
519
+ else if (profileIndex !== undefined) {
284
520
  qs.profileIndex = profileIndex;
285
521
  }
286
522
  else if (profileName) {
@@ -294,10 +530,22 @@ const updateProfileField = async (ctx, i) => {
294
530
  const fieldKey = ctx.getNodeParameter('fieldKey', i);
295
531
  const fieldType = ctx.getNodeParameter('fieldType', i, '');
296
532
  let fieldValue;
533
+ let fileFieldContext;
534
+ let fileProfileSelector;
297
535
  if (FILE_FIELD_TYPES.has(fieldType)) {
298
536
  const binaryPropertyNames = readTypedFieldValue(ctx, i, fieldType);
299
537
  const fileNameOverride = ctx.getNodeParameter('fieldValueFileName', i, '').trim() || undefined;
300
- fieldValue = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, binaryPropertyNames, fieldType, fileNameOverride);
538
+ const fileValueMode = getFileValueMode(ctx, i);
539
+ fileFieldContext = await getPublicUrlFileFieldContext(ctx, fieldKey);
540
+ fileProfileSelector = getEffectiveProfileSelector(fileFieldContext, buildProfileSelector(ctx, i));
541
+ const { existingValues } = await readExistingFileValues(ctx, userId, fieldKey, fileFieldContext, fileProfileSelector, true);
542
+ const uploadCount = countBinaryPropertyNames(binaryPropertyNames);
543
+ if (fileValueMode !== 'replaceIfFull') {
544
+ const expectedValues = fileValueMode === 'replace' ? uploadCount : existingValues.length + uploadCount;
545
+ assertFileValueCountWithinLimit(ctx, fieldKey, expectedValues, fileFieldContext.maxItems);
546
+ }
547
+ const uploadedValues = await (0, shared_1.uploadFilesFromBinaryProperties)(ctx, i, userId, fieldKey, binaryPropertyNames, fieldType, fileNameOverride);
548
+ fieldValue = buildNextFileValues(ctx, fieldKey, existingValues, uploadedValues, fileFieldContext.maxItems, fileValueMode);
301
549
  }
302
550
  else {
303
551
  fieldValue = readTypedFieldValue(ctx, i, fieldType);
@@ -306,19 +554,21 @@ const updateProfileField = async (ctx, i) => {
306
554
  throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `No value provided for custom field "${fieldKey}".`);
307
555
  }
308
556
  const body = { fieldKey, fieldValue };
309
- const profileId = ctx.getNodeParameter('profileId', i, '');
310
- const profileIndex = ctx.getNodeParameter('profileIndex', i, null);
311
- if (profileId) {
312
- body.profileId = profileId;
557
+ let profileSelector = buildProfileSelector(ctx, i);
558
+ if (fileFieldContext) {
559
+ profileSelector = getEffectiveProfileSelector(fileFieldContext, profileSelector);
313
560
  }
314
- else if (profileIndex !== null && Number.isFinite(profileIndex)) {
315
- body.profileIndex = profileIndex;
561
+ else if (!(await cardAllowsMultipleProfiles(ctx, cardId))) {
562
+ profileSelector = {};
316
563
  }
317
- if (body.profileId === undefined && body.profileIndex === undefined) {
318
- const profileName = ctx.getNodeParameter('profileName', i, '');
319
- if (profileName) {
320
- body.profileName = profileName;
321
- }
564
+ if (profileSelector.profileId !== undefined) {
565
+ body.profileId = profileSelector.profileId;
566
+ }
567
+ else if (profileSelector.profileIndex !== undefined) {
568
+ body.profileIndex = profileSelector.profileIndex;
569
+ }
570
+ else if (profileSelector.profileName !== undefined) {
571
+ body.profileName = profileSelector.profileName;
322
572
  }
323
573
  const response = await shared_1.lsRequest.call(ctx, 'PUT', `/custom-fields/store/${userId}/profiles/by-card/${cardId}`, {
324
574
  body,
@@ -335,6 +585,7 @@ exports.customFieldsHandlers = {
335
585
  getFieldValues,
336
586
  setFieldValue,
337
587
  setMultipleFieldValues,
588
+ createFileUploadTarget,
338
589
  getProfiles,
339
590
  getProfilesExpanded,
340
591
  getProfileByCard,
@@ -28,5 +28,5 @@ const sendPushNotification = async (ctx, i) => {
28
28
  });
29
29
  };
30
30
  exports.userHandlers = {
31
- sendPushNotification, // 👈 KEY muss exakt dem operation value entsprechen
31
+ sendPushNotification, // 👈 KEY must exactly match the operation value
32
32
  };
@@ -34,6 +34,17 @@ function buildDesiredFilter(ctx, i, eventType) {
34
34
  var _a;
35
35
  const filter = {};
36
36
  switch (eventType) {
37
+ // ---------------- Agent Action
38
+ case 'agentAction.executed': {
39
+ const col = getCol(ctx, i, 'additionalAgentActionExecuted');
40
+ if (col === null || col === void 0 ? void 0 : col.toolKey) {
41
+ filter.toolKey = String(col.toolKey);
42
+ }
43
+ if (col === null || col === void 0 ? void 0 : col.agentId) {
44
+ filter.agentId = String(col.agentId);
45
+ }
46
+ break;
47
+ }
37
48
  // ---------------- Community
38
49
  case 'communityPost.commented': {
39
50
  const col = getCol(ctx, i, 'additionalCommunityPostCommented');
@@ -161,7 +172,7 @@ function buildDesiredFilter(ctx, i, eventType) {
161
172
  filter.groupId = String(col.groupId);
162
173
  break;
163
174
  }
164
- // ---------------- Lesson Completed (kaskadiert)
175
+ // ---------------- Lesson Completed (cascading)
165
176
  case 'lesson.completed': {
166
177
  const col = getCol(ctx, i, 'additionalLessonCompleted');
167
178
  if (col.courseId)
@@ -223,6 +234,11 @@ const getSampleData = async (ctx, i) => {
223
234
  const sampleDataType = ctx.getNodeParameter('sampleDataType', i);
224
235
  let qs = {};
225
236
  switch (sampleDataType) {
237
+ case 'agent-action-executed-events': {
238
+ const col = ctx.getNodeParameter('additionalAgentActionExecutedSample', i, {});
239
+ qs = col;
240
+ break;
241
+ }
226
242
  case 'progress-changed-events': {
227
243
  const col = ctx.getNodeParameter('additionalProgressChangedSample', i, {});
228
244
  qs = col;
@@ -0,0 +1,3 @@
1
+ import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2
+ export declare function ai_getAgentActions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
3
+ export declare function ai_getAiAgents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ai_getAgentActions = ai_getAgentActions;
4
+ exports.ai_getAiAgents = ai_getAiAgents;
5
+ const common_1 = require("./common");
6
+ async function ai_getAgentActions() {
7
+ return common_1.fetchOptions.call(this, '/agent-actions', undefined, ['name'], ['toolKey']);
8
+ }
9
+ async function ai_getAiAgents() {
10
+ return common_1.fetchOptions.call(this, '/ai-agents', undefined, ['name'], ['id']);
11
+ }
@@ -4,4 +4,5 @@ type LoadOptionRow = IDataObject;
4
4
  export declare function toOptions(rows: LoadOptionRow[], labelKeys?: string[], valueKeys?: string[]): INodePropertyOptions[];
5
5
  export declare function ensureArray<T>(res: T | T[]): T[];
6
6
  export declare function fetchOptions(this: ILoadOptionsFunctions, endpoint: string, qs?: IDataObject, labelKeys?: string[], valueKeys?: string[]): Promise<INodePropertyOptions[]>;
7
+ export declare function fetchOptionsAll(this: ILoadOptionsFunctions, endpoint: string, qs?: IDataObject, labelKeys?: string[], valueKeys?: string[]): Promise<INodePropertyOptions[]>;
7
8
  export {};