n8n-nodes-bgos 1.4.3 → 1.5.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.
@@ -275,6 +275,66 @@ class BGOSAction {
275
275
  ],
276
276
  description: 'Rule: If the message is NOT a direct reply to something the user just typed — cron, scheduler, external webhook, unprompted check-in — use INLINE. If the user just sent a message within the last ~2 min and you want their full attention, MODAL is acceptable. When in doubt, pick INLINE — it never interrupts. Max 6 options for inline.',
277
277
  },
278
+ {
279
+ displayName: 'From Agent',
280
+ name: 'fromAgent',
281
+ type: 'collection',
282
+ placeholder: 'Add Field',
283
+ default: {},
284
+ description: 'Set when this message is being sent BY ANOTHER AI AGENT (n8n LLM, external bot, peer BGOS agent) so it renders distinctly in the BGOS chat (cyan bubble + name+avatar header) and the receiving agent\'s webhook payload carries fromAgent + a systemHint telling its LLM "you are talking to another AI, not a human". Hybrid identity: registered peers (peerId or externalId looked up in agent_peers) win; otherwise inline name/color/avatarUrl are used as-is. Sender field stays USER — fromAgent is the third render flag.',
285
+ options: [
286
+ {
287
+ displayName: 'Peer ID',
288
+ name: 'peerId',
289
+ type: 'number',
290
+ default: 0,
291
+ description: 'agent_peers.id from BGOS registry. If set, registry data wins over inline fields.',
292
+ },
293
+ {
294
+ displayName: 'External ID',
295
+ name: 'externalId',
296
+ type: 'string',
297
+ default: '',
298
+ placeholder: 'marketing-agent-001',
299
+ description: 'Stable string id (per BGOS user) used to look up the peer in the registry. Use when you registered the peer by name and don\'t want to track its numeric id.',
300
+ },
301
+ {
302
+ displayName: 'Name',
303
+ name: 'name',
304
+ type: 'string',
305
+ default: '',
306
+ placeholder: 'Marketing Agent',
307
+ description: 'Display name shown in the cyan bubble header. Inline fallback used when no registered peer matches.',
308
+ },
309
+ {
310
+ displayName: 'Color',
311
+ name: 'color',
312
+ type: 'color',
313
+ default: '',
314
+ description: 'Bubble color hex (e.g. #0EA5E9). Inline fallback. Defaults to BGOS cyan when omitted.',
315
+ },
316
+ {
317
+ displayName: 'Avatar URL',
318
+ name: 'avatarUrl',
319
+ type: 'string',
320
+ default: '',
321
+ placeholder: 'https://...',
322
+ description: 'Avatar URL. Inline fallback.',
323
+ },
324
+ {
325
+ displayName: 'Agent Type',
326
+ name: 'type',
327
+ type: 'options',
328
+ default: 'n8n',
329
+ options: [
330
+ { name: 'n8n workflow', value: 'n8n' },
331
+ { name: 'BGOS assistant', value: 'bgos' },
332
+ { name: 'External', value: 'external' },
333
+ ],
334
+ description: 'Origin of the agent. Influences the default systemHint sent to the receiving agent.',
335
+ },
336
+ ],
337
+ },
278
338
  ],
279
339
  },
280
340
  // ── Delete a Message ──────────────────────────────────────────────────
@@ -412,6 +472,66 @@ class BGOSAction {
412
472
  default: 2000,
413
473
  description: 'How often to check for new messages while waiting (default 2 seconds; minimum 1000).',
414
474
  },
475
+ {
476
+ displayName: 'From Agent',
477
+ name: 'fromAgent',
478
+ type: 'collection',
479
+ placeholder: 'Add Field',
480
+ default: {},
481
+ description: 'Identify the sending peer agent so the message renders distinctly (cyan bubble + header) AND the receiving agent\'s webhook payload includes fromAgent + a systemHint. Hybrid: registry (peerId/externalId) > inline (name/color/avatarUrl). The Send-and-Wait poll loop is unaffected — it still reads the next reply from the chat history regardless of fromAgent.',
482
+ options: [
483
+ {
484
+ displayName: 'Peer ID',
485
+ name: 'peerId',
486
+ type: 'number',
487
+ default: 0,
488
+ description: 'agent_peers.id from BGOS registry. Registry wins over inline.',
489
+ },
490
+ {
491
+ displayName: 'External ID',
492
+ name: 'externalId',
493
+ type: 'string',
494
+ default: '',
495
+ placeholder: 'marketing-agent-001',
496
+ description: 'Stable string id used to look up the peer in the registry.',
497
+ },
498
+ {
499
+ displayName: 'Name',
500
+ name: 'name',
501
+ type: 'string',
502
+ default: '',
503
+ placeholder: 'Marketing Agent',
504
+ description: 'Display name. Inline fallback when no registry match.',
505
+ },
506
+ {
507
+ displayName: 'Color',
508
+ name: 'color',
509
+ type: 'color',
510
+ default: '',
511
+ description: 'Bubble color hex e.g. #0EA5E9. Inline fallback.',
512
+ },
513
+ {
514
+ displayName: 'Avatar URL',
515
+ name: 'avatarUrl',
516
+ type: 'string',
517
+ default: '',
518
+ placeholder: 'https://...',
519
+ description: 'Avatar URL. Inline fallback.',
520
+ },
521
+ {
522
+ displayName: 'Agent Type',
523
+ name: 'type',
524
+ type: 'options',
525
+ default: 'n8n',
526
+ options: [
527
+ { name: 'n8n workflow', value: 'n8n' },
528
+ { name: 'BGOS assistant', value: 'bgos' },
529
+ { name: 'External', value: 'external' },
530
+ ],
531
+ description: 'Origin of the agent.',
532
+ },
533
+ ],
534
+ },
415
535
  ],
416
536
  },
417
537
  // ── Ask User Input ────────────────────────────────────────────────────
@@ -758,6 +878,12 @@ class BGOSAction {
758
878
  nodeParams.renderMode = additionalFields.renderMode
759
879
  ? String(additionalFields.renderMode)
760
880
  : 'inline';
881
+ // Forward the From Agent collection block (peerId/externalId/
882
+ // name/color/avatarUrl/type). Backend's hybrid resolver picks
883
+ // registry > inline; absence falls through to a normal user msg.
884
+ if (additionalFields.fromAgent && typeof additionalFields.fromAgent === 'object') {
885
+ nodeParams.fromAgent = additionalFields.fromAgent;
886
+ }
761
887
  }
762
888
  if (operation === 'deleteMessage') {
763
889
  nodeParams.deleteMessageId = String(this.getNodeParameter('deleteMessageId', i, '') ?? '');
@@ -781,6 +907,9 @@ class BGOSAction {
781
907
  nodeParams.fileId = String(replyAdditional.fileId ?? '');
782
908
  nodeParams.replyTimeoutSeconds = Number(replyAdditional.replyTimeoutSeconds ?? 600);
783
909
  nodeParams.replyPollIntervalMs = Number(replyAdditional.replyPollIntervalMs ?? 2000);
910
+ if (replyAdditional.fromAgent && typeof replyAdditional.fromAgent === 'object') {
911
+ nodeParams.fromAgent = replyAdditional.fromAgent;
912
+ }
784
913
  }
785
914
  if (operation === 'getChat') {
786
915
  nodeParams.chatId = String(this.getNodeParameter('chatId', i, '') ?? '');
@@ -9,6 +9,38 @@ function getApiOptions(nodeParams) {
9
9
  }
10
10
  return { baseUrl, apiKey: nodeParams.apiKey };
11
11
  }
12
+ /**
13
+ * Strip empty values from the `fromAgent` collection so an empty UI block
14
+ * doesn't accidentally trigger the agent-render path on the backend. Returns
15
+ * undefined when there's nothing meaningful to send.
16
+ */
17
+ function sanitizeFromAgent(raw) {
18
+ if (!raw || typeof raw !== 'object')
19
+ return undefined;
20
+ const obj = raw;
21
+ const peerId = typeof obj.peerId === 'number' && obj.peerId > 0 ? obj.peerId : undefined;
22
+ const externalId = typeof obj.externalId === 'string' && obj.externalId.trim() ? obj.externalId.trim() : undefined;
23
+ const name = typeof obj.name === 'string' && obj.name.trim() ? obj.name.trim() : undefined;
24
+ const color = typeof obj.color === 'string' && obj.color.trim() ? obj.color.trim() : undefined;
25
+ const avatarUrl = typeof obj.avatarUrl === 'string' && obj.avatarUrl.trim() ? obj.avatarUrl.trim() : undefined;
26
+ const type = typeof obj.type === 'string' && obj.type.trim() ? obj.type.trim() : undefined;
27
+ if (!peerId && !externalId && !name && !color && !avatarUrl && !type)
28
+ return undefined;
29
+ const out = {};
30
+ if (peerId !== undefined)
31
+ out.peerId = peerId;
32
+ if (externalId !== undefined)
33
+ out.externalId = externalId;
34
+ if (name !== undefined)
35
+ out.name = name;
36
+ if (color !== undefined)
37
+ out.color = color;
38
+ if (avatarUrl !== undefined)
39
+ out.avatarUrl = avatarUrl;
40
+ if (type !== undefined)
41
+ out.type = type;
42
+ return out;
43
+ }
12
44
  async function handleEventByType(eventType, eventData, nodeParams) {
13
45
  const operation = nodeParams.operation;
14
46
  const assistant = eventData.assistant;
@@ -54,6 +86,7 @@ async function handleEventByType(eventType, eventData, nodeParams) {
54
86
  ?? eventData.renderMode
55
87
  ?? eventData.render_mode
56
88
  ?? 'inline');
89
+ const fromAgent = sanitizeFromAgent(nodeParams.fromAgent);
57
90
  return await techWebhook_1.sendMessageToBackend.call(this, apiOptions, {
58
91
  assistantId: assistantId,
59
92
  chatId: chatId,
@@ -71,6 +104,7 @@ async function handleEventByType(eventType, eventData, nodeParams) {
71
104
  audioMimeType: (eventData.audioMimeType ?? message?.audioMimeType ?? null),
72
105
  audioDuration: (eventData.audioDuration ?? message?.audioDuration ?? null),
73
106
  isMixedAttachments: files.length > 0 && text ? true : (eventData.isMixedAttachments ?? message?.isMixedAttachments ?? null),
107
+ ...(fromAgent ? { fromAgent } : {}),
74
108
  });
75
109
  }
76
110
  case 'deleteMessage': {
@@ -115,7 +149,12 @@ async function handleEventByType(eventType, eventData, nodeParams) {
115
149
  const fileIdParam = nodeParams.fileId?.trim();
116
150
  const files = fileIdParam ? [{ id: fileIdParam }] : [];
117
151
  const sender = nodeParams.sender || 'assistant';
152
+ const fromAgentReply = sanitizeFromAgent(nodeParams.fromAgent);
118
153
  // Step 1: post the outgoing message and capture its id
154
+ // NOTE: fromAgent flows through here — it's persisted on the request
155
+ // message and forwarded on the receiving agent's webhook payload, but
156
+ // does NOT affect the polling loop below (which only reads sentMessageId
157
+ // and the next message's id+text).
119
158
  const sendResult = (await techWebhook_1.sendMessageToBackend.call(this, apiOptions, {
120
159
  assistantId: assistantId,
121
160
  chatId: chatId,
@@ -123,6 +162,7 @@ async function handleEventByType(eventType, eventData, nodeParams) {
123
162
  text,
124
163
  files,
125
164
  isMixedAttachments: files.length > 0 ? true : null,
165
+ ...(fromAgentReply ? { fromAgent: fromAgentReply } : {}),
126
166
  }));
127
167
  const sentMessageId = Number(sendResult?.id
128
168
  ?? sendResult?.message?.id
@@ -167,6 +167,12 @@ async function sendMessageToBackend(options, payload) {
167
167
  };
168
168
  if (payload.renderMode)
169
169
  body.renderMode = payload.renderMode;
170
+ // Forward the peer-agent identity block when the workflow set it. Backend's
171
+ // hybrid resolver picks registry vs inline; either form makes the BGOS UI
172
+ // render the cyan agent bubble + name header on this message.
173
+ if (payload.fromAgent && typeof payload.fromAgent === 'object') {
174
+ body.fromAgent = payload.fromAgent;
175
+ }
170
176
  return this.helpers.httpRequest({
171
177
  method: 'POST',
172
178
  url,
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-bgos",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "description": "n8n community nodes for BGOS (Brand Growth OS) - AI assistant chat platform",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-bgos",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "description": "n8n community nodes for BGOS (Brand Growth OS) - AI assistant chat platform",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",