n8n-nodes-bgos 1.3.1 → 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.
package/README.md CHANGED
@@ -16,6 +16,37 @@ Perform operations on the BGOS platform:
16
16
  | **User** | Get or Create, Update |
17
17
  | **Scheduled Task** | Get, Get User Tasks, Delete |
18
18
 
19
+ #### Render Mode — Inline Buttons vs. Modal Pop-Under
20
+
21
+ Two ways the BGOS app renders buttons that come with a message:
22
+
23
+ | Mode | Looks Like | When It Makes Sense |
24
+ |---|---|---|
25
+ | **Inline** (default on `sendMessage`) | Telegram-style chips inside a warm off-white card below the message. Skip (top-left), "✍ Custom reply" footer link. Stays clickable forever — user can answer now, tomorrow, or never. | The message is **not** a direct reply to something the user just typed: cron, scheduler, external webhook, unprompted check-in. Single question, up to 6 labeled choices. |
26
+ | **Modal** (default on `askUserInput`) | Bottom sheet / pop-under that demands attention. Multi-question carousel, free-text fallback, skip-all. | Active back-and-forth conversation — user just sent a message and you need their answer to continue. Also use for multi-question flows. |
27
+
28
+ **Default to inline unless you know the user is actively in the chat.** Modals interrupt. Inline never does.
29
+
30
+ ##### Decision tree for AI agents
31
+
32
+ ```
33
+ Is this message triggered by a user message they sent in the last ~2 minutes?
34
+ ├─ YES → modal OK (but inline also works)
35
+ └─ NO → inline. Always.
36
+
37
+ Do you need multi-question carousel, free-text, OR skip-all semantics?
38
+ ├─ YES → must use modal (askUserInput op with askRenderMode=modal)
39
+ └─ NO → prefer inline
40
+ ```
41
+
42
+ ##### Agent-facing contract
43
+
44
+ - **Inline** chips ≤ 6 (backend rejects more). Labels ≤ 24 chars render cleanly; longer wraps.
45
+ - When the user clicks a chip, your `BGOS Trigger` receives `button_clicked` with `callback_data` = the option's `value`.
46
+ - When the user taps **Skip**, `button_clicked` fires with `callback_data: "__skip__"` and `option_id: null`.
47
+ - When the user taps **Custom reply** and submits free text, they get **two** events: a normal user `message` AND a `button_clicked` with `callback_data: "__custom__"` + `custom_text: <what they typed>`. Correlate by `message_id`.
48
+ - Respond to button clicks with the **Answer a Callback** operation so the chip animation completes.
49
+
19
50
  #### Ask User Input
20
51
 
21
52
  Pops the BGOS app's polished modal/sheet to ask the user 1–4 multiple-choice questions in a single carousel. Each question accepts: option list (label + value), optional free-text fallback, optional skip. The node POSTs each question to `/api/v1/messages` with `messageType=ask_user_input`, sharing one `askId` so they group into one carousel.
@@ -7,7 +7,7 @@ class BGOSAction {
7
7
  description = {
8
8
  displayName: 'BGOS Action',
9
9
  name: 'bgosAction',
10
- icon: 'file:bgos.svg',
10
+ icon: 'file:bgos-icon.svg',
11
11
  group: ['transform'],
12
12
  version: 1,
13
13
  subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
@@ -228,6 +228,17 @@ class BGOSAction {
228
228
  placeholder: '[{"label":"Yes","value":"yes"},{"label":"No","value":"no"}]',
229
229
  description: 'JSON array of button options to display with the message',
230
230
  },
231
+ {
232
+ displayName: 'Render Mode',
233
+ name: 'renderMode',
234
+ type: 'options',
235
+ default: 'inline',
236
+ options: [
237
+ { name: 'Inline buttons (Telegram-style, asynchronous)', value: 'inline' },
238
+ { name: 'Modal pop-under (interactive, blocks UI)', value: 'modal' },
239
+ ],
240
+ 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.',
241
+ },
231
242
  ],
232
243
  },
233
244
  // ── Delete a Message ──────────────────────────────────────────────────
@@ -340,6 +351,18 @@ class BGOSAction {
340
351
  description: 'Whether the node should block until every question is answered (or the timeout expires) and return structured answers. When disabled, the node returns immediately after posting; subscribe to the Ask Response event in a BGOS Trigger to receive each answer.',
341
352
  displayOptions: { show: { resource: ['message'], operation: ['askUserInput'] } },
342
353
  },
354
+ {
355
+ displayName: 'Render Mode',
356
+ name: 'askRenderMode',
357
+ type: 'options',
358
+ default: 'modal',
359
+ options: [
360
+ { name: 'Modal pop-under (interactive, blocks UI)', value: 'modal' },
361
+ { name: 'Inline buttons (Telegram-style, asynchronous)', value: 'inline' },
362
+ ],
363
+ description: 'MODAL is the default because askUserInput is typically used mid-conversation. Switch to INLINE if this ask is triggered by cron/schedule/external webhook, or if you do NOT want to interrupt the user. Note: only MODAL supports multi-question carousel, free-text, and skip-all. INLINE renders 1 question with up to 6 chips plus a "Custom reply" / "Skip" footer.',
364
+ displayOptions: { show: { resource: ['message'], operation: ['askUserInput'] } },
365
+ },
343
366
  {
344
367
  displayName: 'Additional Fields',
345
368
  name: 'askAdditionalFields',
@@ -569,6 +592,9 @@ class BGOSAction {
569
592
  nodeParams.fileId = String(additionalFields.fileId ?? '');
570
593
  nodeParams.sender = String(additionalFields.sender ?? 'assistant');
571
594
  nodeParams.options = String(additionalFields.options ?? '');
595
+ nodeParams.renderMode = additionalFields.renderMode
596
+ ? String(additionalFields.renderMode)
597
+ : 'inline';
572
598
  }
573
599
  if (operation === 'deleteMessage') {
574
600
  nodeParams.deleteMessageId = String(this.getNodeParameter('deleteMessageId', i, '') ?? '');
@@ -577,6 +603,7 @@ class BGOSAction {
577
603
  nodeParams.chatId = String(this.getNodeParameter('chatId', i, '') ?? '');
578
604
  nodeParams.userId = String(this.getNodeParameter('askUserId', i, '') ?? '');
579
605
  nodeParams.waitForAnswers = Boolean(this.getNodeParameter('waitForAnswers', i, true));
606
+ nodeParams.renderMode = String(this.getNodeParameter('askRenderMode', i, 'modal') ?? 'modal');
580
607
  const askQuestionsRaw = this.getNodeParameter('askQuestions', i, {});
581
608
  const questions = (askQuestionsRaw.question ?? []).map((q) => ({
582
609
  text: String(q.text ?? '').trim(),
@@ -37,6 +37,7 @@ type NodeParams = {
37
37
  waitForAnswers?: boolean;
38
38
  askTimeoutSeconds?: number;
39
39
  askPollIntervalMs?: number;
40
+ renderMode?: string;
40
41
  [key: string]: unknown;
41
42
  };
42
43
  export declare function handleEventByType(this: IExecuteFunctions, eventType: string, eventData: Record<string, unknown>, nodeParams: NodeParams): Promise<unknown>;
@@ -50,6 +50,10 @@ async function handleEventByType(eventType, eventData, nodeParams) {
50
50
  else if (Array.isArray(eventData.options)) {
51
51
  buttonOptions = eventData.options;
52
52
  }
53
+ const renderMode = (nodeParams.renderMode
54
+ ?? eventData.renderMode
55
+ ?? eventData.render_mode
56
+ ?? 'inline');
53
57
  return await techWebhook_1.sendMessageToBackend.call(this, apiOptions, {
54
58
  assistantId: assistantId,
55
59
  chatId: chatId,
@@ -57,6 +61,7 @@ async function handleEventByType(eventType, eventData, nodeParams) {
57
61
  text,
58
62
  options: buttonOptions,
59
63
  files,
64
+ renderMode,
60
65
  isCode: (eventData.isCode ?? message?.isCode ?? null),
61
66
  artifactCode: (eventData.artifactCode ?? message?.artifactCode ?? null),
62
67
  isArticle: (eventData.isArticle ?? message?.isArticle ?? null),
@@ -108,6 +113,10 @@ async function handleEventByType(eventType, eventData, nodeParams) {
108
113
  if (!q.options.length)
109
114
  throw new Error(`Question ${idx + 1}: at least one option is required.`);
110
115
  }
116
+ const renderMode = (nodeParams.renderMode
117
+ ?? eventData.renderMode
118
+ ?? eventData.render_mode
119
+ ?? 'modal');
111
120
  // Post each question. The first call's response carries the
112
121
  // backend-generated askId; reuse it for the rest so they group into
113
122
  // one carousel/sheet on the frontend.
@@ -124,6 +133,7 @@ async function handleEventByType(eventType, eventData, nodeParams) {
124
133
  allowFreeText: q.allowFreeText,
125
134
  allowSkip: q.allowSkip,
126
135
  options: q.options,
136
+ renderMode,
127
137
  });
128
138
  postedIds.push(posted.id);
129
139
  if (!askId && posted.askId)
@@ -25,6 +25,7 @@ export declare function sendMessageToBackend(this: IExecuteFunctions, options: B
25
25
  text?: string | null;
26
26
  options?: unknown[];
27
27
  files?: unknown[];
28
+ renderMode?: string | null;
28
29
  isCode?: boolean | null;
29
30
  artifactCode?: string | null;
30
31
  isArticle?: boolean | null;
@@ -67,6 +68,7 @@ export declare function postAskQuestion(this: IExecuteFunctions, options: BgosAp
67
68
  label: string;
68
69
  value: string;
69
70
  }>;
71
+ renderMode?: string | null;
70
72
  }): Promise<AskQuestionPosted>;
71
73
  export interface ChatMessageEntry {
72
74
  message: {
@@ -117,6 +117,8 @@ async function sendMessageToBackend(options, payload) {
117
117
  options: payload.options ?? [],
118
118
  files: payload.files ?? [],
119
119
  };
120
+ if (payload.renderMode)
121
+ body.renderMode = payload.renderMode;
120
122
  return this.helpers.httpRequest({
121
123
  method: 'POST',
122
124
  url,
@@ -148,6 +150,8 @@ async function postAskQuestion(options, payload) {
148
150
  };
149
151
  if (payload.askId)
150
152
  body.askId = payload.askId;
153
+ if (payload.renderMode)
154
+ body.renderMode = payload.renderMode;
151
155
  const result = (await this.helpers.httpRequest({
152
156
  method: 'POST',
153
157
  url,
@@ -52,7 +52,7 @@ class Bgos {
52
52
  description = {
53
53
  displayName: 'BGOS',
54
54
  name: 'bgos',
55
- icon: 'file:bgos.svg',
55
+ icon: 'file:bgos-icon.svg',
56
56
  group: ['transform'],
57
57
  version: 1,
58
58
  subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
@@ -26,7 +26,7 @@ class BgosTrigger {
26
26
  description = {
27
27
  displayName: 'BGOS Trigger',
28
28
  name: 'bgosTrigger',
29
- icon: 'file:bgos.svg',
29
+ icon: 'file:bgos-icon.svg',
30
30
  group: ['trigger'],
31
31
  version: 1,
32
32
  subtitle: '={{$parameter["updates"]}}',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-bgos",
3
- "version": "1.3.1",
3
+ "version": "1.3.4",
4
4
  "description": "n8n community nodes for BGOS (Brand Growth OS) - AI assistant chat platform",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
File without changes
File without changes
File without changes
File without changes
File without changes