n8n-nodes-bgos 1.4.2 → 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.
|
@@ -14,15 +14,13 @@ class BGOSAction {
|
|
|
14
14
|
name: 'bgosAction',
|
|
15
15
|
icon: 'file:bgos-icon.svg',
|
|
16
16
|
group: ['transform'],
|
|
17
|
-
// n8n typeVersion.
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
// (Sender / File ID / User ID moved into collection)
|
|
25
|
-
version: [1, 1.1, 1.2],
|
|
17
|
+
// n8n typeVersion. INTEGER ONLY — some n8n deployments store this as a
|
|
18
|
+
// postgres INT column on package install and reject fractional/array
|
|
19
|
+
// forms with "invalid input syntax for type integer". Bump only on
|
|
20
|
+
// BREAKING schema changes (with migration logic). For non-breaking
|
|
21
|
+
// additions, leave at 1 and surface the npm package version via the
|
|
22
|
+
// description string below — that's what users actually want to see.
|
|
23
|
+
version: 1,
|
|
26
24
|
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
|
27
25
|
description: `Interact with the BGOS chat platform. Send/edit/pin/delete messages, ask the user a question, send a message and wait for the reply, fetch chat metadata, manage assistants and files. Usable as an AI agent tool. (n8n-nodes-bgos v${package_json_1.version})`,
|
|
28
26
|
defaults: { name: 'BGOS Action' },
|
|
@@ -277,6 +275,66 @@ class BGOSAction {
|
|
|
277
275
|
],
|
|
278
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.',
|
|
279
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
|
+
},
|
|
280
338
|
],
|
|
281
339
|
},
|
|
282
340
|
// ── Delete a Message ──────────────────────────────────────────────────
|
|
@@ -414,6 +472,66 @@ class BGOSAction {
|
|
|
414
472
|
default: 2000,
|
|
415
473
|
description: 'How often to check for new messages while waiting (default 2 seconds; minimum 1000).',
|
|
416
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
|
+
},
|
|
417
535
|
],
|
|
418
536
|
},
|
|
419
537
|
// ── Ask User Input ────────────────────────────────────────────────────
|
|
@@ -760,6 +878,12 @@ class BGOSAction {
|
|
|
760
878
|
nodeParams.renderMode = additionalFields.renderMode
|
|
761
879
|
? String(additionalFields.renderMode)
|
|
762
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
|
+
}
|
|
763
887
|
}
|
|
764
888
|
if (operation === 'deleteMessage') {
|
|
765
889
|
nodeParams.deleteMessageId = String(this.getNodeParameter('deleteMessageId', i, '') ?? '');
|
|
@@ -783,6 +907,9 @@ class BGOSAction {
|
|
|
783
907
|
nodeParams.fileId = String(replyAdditional.fileId ?? '');
|
|
784
908
|
nodeParams.replyTimeoutSeconds = Number(replyAdditional.replyTimeoutSeconds ?? 600);
|
|
785
909
|
nodeParams.replyPollIntervalMs = Number(replyAdditional.replyPollIntervalMs ?? 2000);
|
|
910
|
+
if (replyAdditional.fromAgent && typeof replyAdditional.fromAgent === 'object') {
|
|
911
|
+
nodeParams.fromAgent = replyAdditional.fromAgent;
|
|
912
|
+
}
|
|
786
913
|
}
|
|
787
914
|
if (operation === 'getChat') {
|
|
788
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