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