@hostwebhook/node-types 1.69.0 → 1.70.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.
Files changed (92) hide show
  1. package/dist/calendar-toolkit.d.ts +1 -1
  2. package/dist/connections.d.ts +1 -1
  3. package/dist/discord-operations.d.ts +1 -1
  4. package/dist/discord-operations.js +4 -4
  5. package/dist/discord-toolkit.d.ts +1 -1
  6. package/dist/discord-toolkit.js +3 -3
  7. package/dist/dispatch.d.ts +1 -1
  8. package/dist/docs-toolkit.d.ts +1 -1
  9. package/dist/drive-toolkit.d.ts +1 -1
  10. package/dist/esm/calendar-operations.d.ts +67 -0
  11. package/dist/esm/calendar-operations.js +176 -0
  12. package/dist/esm/calendar-toolkit.d.ts +50 -0
  13. package/dist/esm/calendar-toolkit.js +152 -0
  14. package/dist/esm/capacidad-de-credencial.d.ts +75 -0
  15. package/dist/esm/capacidad-de-credencial.js +68 -0
  16. package/dist/esm/connections.d.ts +17 -0
  17. package/dist/esm/connections.js +106 -0
  18. package/dist/esm/credentials.d.ts +144 -0
  19. package/dist/esm/credentials.js +199 -0
  20. package/dist/esm/discord-operations.d.ts +104 -0
  21. package/dist/esm/discord-operations.js +407 -0
  22. package/dist/esm/discord-toolkit.d.ts +64 -0
  23. package/dist/esm/discord-toolkit.js +260 -0
  24. package/dist/esm/dispatch.d.ts +16 -0
  25. package/dist/esm/dispatch.js +76 -0
  26. package/dist/esm/docs-operations.d.ts +99 -0
  27. package/dist/esm/docs-operations.js +127 -0
  28. package/dist/esm/docs-toolkit.d.ts +42 -0
  29. package/dist/esm/docs-toolkit.js +97 -0
  30. package/dist/esm/drive-operations.d.ts +103 -0
  31. package/dist/esm/drive-operations.js +319 -0
  32. package/dist/esm/drive-toolkit.d.ts +54 -0
  33. package/dist/esm/drive-toolkit.js +177 -0
  34. package/dist/esm/github-operations.d.ts +147 -0
  35. package/dist/esm/github-operations.js +551 -0
  36. package/dist/esm/gmail-operations.d.ts +190 -0
  37. package/dist/esm/gmail-operations.js +560 -0
  38. package/dist/esm/google-analytics-operations.d.ts +99 -0
  39. package/dist/esm/google-analytics-operations.js +222 -0
  40. package/dist/esm/google-contacts-operations.d.ts +93 -0
  41. package/dist/esm/google-contacts-operations.js +363 -0
  42. package/dist/esm/index.d.ts +66 -0
  43. package/dist/esm/index.js +50 -0
  44. package/dist/esm/jira-operations.d.ts +164 -0
  45. package/dist/esm/jira-operations.js +470 -0
  46. package/dist/esm/llm-models.d.ts +101 -0
  47. package/dist/esm/llm-models.js +230 -0
  48. package/dist/esm/mailchimp-operations.d.ts +56 -0
  49. package/dist/esm/mailchimp-operations.js +155 -0
  50. package/dist/esm/mongo-operations.d.ts +61 -0
  51. package/dist/esm/mongo-operations.js +120 -0
  52. package/dist/esm/notion-operations.d.ts +125 -0
  53. package/dist/esm/notion-operations.js +511 -0
  54. package/dist/esm/openrouter.d.ts +142 -0
  55. package/dist/esm/openrouter.js +99 -0
  56. package/dist/esm/package.json +4 -0
  57. package/dist/esm/postgres-operations.d.ts +60 -0
  58. package/dist/esm/postgres-operations.js +75 -0
  59. package/dist/esm/registry.d.ts +160 -0
  60. package/dist/esm/registry.js +555 -0
  61. package/dist/esm/sheets-operations.d.ts +78 -0
  62. package/dist/esm/sheets-operations.js +150 -0
  63. package/dist/esm/sheets-toolkit.d.ts +49 -0
  64. package/dist/esm/sheets-toolkit.js +127 -0
  65. package/dist/esm/shopify-operations.d.ts +95 -0
  66. package/dist/esm/shopify-operations.js +259 -0
  67. package/dist/esm/slack-operations.d.ts +81 -0
  68. package/dist/esm/slack-operations.js +244 -0
  69. package/dist/esm/slack-toolkit.d.ts +60 -0
  70. package/dist/esm/slack-toolkit.js +235 -0
  71. package/dist/esm/telegram-operations.d.ts +104 -0
  72. package/dist/esm/telegram-operations.js +351 -0
  73. package/dist/esm/telegram-toolkit.d.ts +52 -0
  74. package/dist/esm/telegram-toolkit.js +183 -0
  75. package/dist/esm/types.d.ts +149 -0
  76. package/dist/esm/types.js +8 -0
  77. package/dist/esm/ui.d.ts +13 -0
  78. package/dist/esm/ui.js +150 -0
  79. package/dist/esm/whatsapp-operations.d.ts +23 -0
  80. package/dist/esm/whatsapp-operations.js +36 -0
  81. package/dist/index.d.ts +66 -66
  82. package/dist/index.js +185 -185
  83. package/dist/openrouter.d.ts +1 -1
  84. package/dist/registry.d.ts +1 -1
  85. package/dist/sheets-toolkit.d.ts +1 -1
  86. package/dist/slack-operations.d.ts +1 -1
  87. package/dist/slack-operations.js +4 -4
  88. package/dist/slack-toolkit.d.ts +1 -1
  89. package/dist/slack-toolkit.js +3 -3
  90. package/dist/telegram-toolkit.d.ts +1 -1
  91. package/dist/ui.d.ts +1 -1
  92. package/package.json +21 -3
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Telegram AI Toolkit — las herramientas que un telegramAction expone cuando
3
+ * `aiEnabled` está encendido.
4
+ *
5
+ * **Se escribe una sola vez**, como el de Sheets. Antes vivía dos veces: una
6
+ * copia en `dashboard/components/telegram-actions/telegram-operations-schemas.ts`
7
+ * y otra en `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano, las dos con
8
+ * el mismo TODO escrito pidiendo justo esto. Y ya habían derivado: el
9
+ * `messageId` de `reply_telegram_message` era `number` en el dashboard y
10
+ * `string` en la api, mientras en las otras tres ops que lo llevan era `number`
11
+ * en las dos. Aquí es `number` y punto.
12
+ *
13
+ * Hay una TERCERA copia que también desaparece: la página de detalle pintaba la
14
+ * lista de las diez herramientas como diez `<li>` escritos a mano. Ahora sale de
15
+ * aquí, contada con `.length`, que es lo que evita que un día anuncie diez y
16
+ * exponga otra cosa.
17
+ *
18
+ * La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que pueda
19
+ * usarse sin adaptador. Los textos van en inglés porque los lee el modelo y
20
+ * quien mire la lista de herramientas del servidor MCP.
21
+ */
22
+ const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
23
+ /*
24
+ * `sendAndWaitForResponse` NO está aquí, y es una decisión, no un olvido: la op
25
+ * para el pipeline durante minutos u horas esperando que alguien pulse un botón,
26
+ * y por la vía de una herramienta no espera — el modelo recibe algo inmediato y
27
+ * acaba diciendo que ya se aprobó. Se usa en modo single-op, con un Conditional
28
+ * detrás leyendo `_waitResponse.approved`. La espera se resuelve por
29
+ * `triggers.processTelegramPush`, donde el LLM no participa.
30
+ *
31
+ * Ninguna lleva `destructive`. `delete_telegram_message` lo merecería —el
32
+ * toolkit de Drive marca sus borrados— pero hoy la spec de Telegram no tiene ese
33
+ * campo en ninguna de las dos capas, y añadirlo cambiaría cuándo el AI Node pide
34
+ * confirmación. Eso es otra decisión, no parte de mudar el esquema de sitio.
35
+ */
36
+ export const TELEGRAM_TOOLKIT_SPECS = [
37
+ {
38
+ operation: 'sendMessage',
39
+ label: 'Send message',
40
+ toolName: 'send_telegram_message',
41
+ group: 'Sending',
42
+ description: 'Send a text message via the Telegram bot to a chat. Use for replies, notifications, status updates, or any text-only delivery. ' +
43
+ 'Supports HTML formatting (default parse mode). For inline buttons, supply the replyMarkup arg as a 2D array of buttons. ' +
44
+ 'USAGE RULES: ' +
45
+ '(1) chatId is REQUIRED — when replying inside a flow triggered by a Telegram message, this is normally `{{payload.chatId}}` from the trigger payload. ' +
46
+ '(2) Telegram caps text at 4096 chars. Split longer messages or use sendDocument with a text file. ' +
47
+ '(3) For a quote-style reply that points at a SPECIFIC user message, prefer `reply_telegram_message` so Telegram renders the threaded indicator — this op sends a standalone message.',
48
+ parameters: [
49
+ p('chatId', 'Target chat id (number) or @channelusername (string).'),
50
+ p('text', 'Message text. HTML by default — escape <, >, & in user content.'),
51
+ p('parseMode', 'Optional. "HTML" (default), "MarkdownV2", or "none".', false),
52
+ p('replyToMessageId', 'Optional. Reply to a specific message id.', false),
53
+ ],
54
+ },
55
+ {
56
+ operation: 'replyToMessage',
57
+ label: 'Reply to message',
58
+ toolName: 'reply_telegram_message',
59
+ group: 'Sending',
60
+ description: 'Reply to a SPECIFIC user message with the quote indicator visible. Telegram renders the bot\'s reply with a small "replying to..." card above it pointing back at the quoted message. ' +
61
+ 'USAGE RULES: ' +
62
+ '(1) Use this in group chats where multiple users post in parallel — the indicator makes it clear which user the bot is answering. In 1:1 DMs only use it when context disambiguates between several recent questions. ' +
63
+ '(2) messageId is REQUIRED — pass the id of the user\'s message you\'re responding to (typically `{{payload.message.message_id}}` or `{{payload.message_id}}` from the trigger). ' +
64
+ '(3) chatId is REQUIRED and must match the chat the original message lives in. ' +
65
+ '(4) Same Telegram /sendMessage webhook underneath — text limit (4096 chars) and parse modes (HTML / MarkdownV2 / none) work the same.',
66
+ parameters: [
67
+ p('chatId', 'Target chat id (number) or @channelusername — must match the chat of the quoted message.'),
68
+ // `number`, como en las otras tres ops que llevan messageId. La api lo
69
+ // declaraba `string` por un cuarto argumento que se quedó sin escribir.
70
+ p('messageId', 'Numeric id of the message to quote-reply to. Telegram renders the threaded indicator from this.', true, 'number'),
71
+ p('text', 'Reply text. HTML by default.'),
72
+ p('parseMode', 'Optional. "HTML" (default), "MarkdownV2", or "none".', false),
73
+ ],
74
+ },
75
+ {
76
+ operation: 'sendPhoto',
77
+ label: 'Send photo',
78
+ toolName: 'send_telegram_photo',
79
+ group: 'Sending',
80
+ description: 'Send a photo to a chat. The photo arg accepts a public URL (Telegram fetches it) or a Telegram file_id from a previously-sent photo (cheap re-send). ' +
81
+ 'For HW-stored files use the `_file.downloadUrl` template. Max 10MB photo size when fetching by URL. Caption is optional and supports HTML.',
82
+ parameters: [
83
+ p('chatId', 'Target chat id or @channelusername.'),
84
+ p('photo', "Public URL OR file_id from a prior send. URLs must be reachable from Telegram's servers."),
85
+ p('caption', 'Optional caption (max 1024 chars, HTML supported).', false),
86
+ p('parseMode', 'Optional caption parse mode: HTML / MarkdownV2 / none.', false),
87
+ ],
88
+ },
89
+ {
90
+ operation: 'sendDocument',
91
+ label: 'Send document',
92
+ toolName: 'send_telegram_document',
93
+ group: 'Sending',
94
+ description: 'Send a document (any file type) to a chat. Same URL-or-file_id contract as sendPhoto. Max 50MB when Telegram fetches by URL.',
95
+ parameters: [
96
+ p('chatId', 'Target chat id or @channelusername.'),
97
+ p('document', 'Public URL OR file_id.'),
98
+ p('caption', 'Optional caption (HTML supported).', false),
99
+ p('parseMode', 'Optional caption parse mode.', false),
100
+ ],
101
+ },
102
+ {
103
+ operation: 'sendVoice',
104
+ label: 'Send voice',
105
+ toolName: 'send_telegram_voice',
106
+ group: 'Sending',
107
+ description: 'Send a voice message — Telegram renders it as a voice bubble with waveform. ' +
108
+ 'USAGE RULES: ' +
109
+ '(1) chatId is REQUIRED — usually `{{payload.chatId}}` from the trigger. ' +
110
+ '(2) voice is REQUIRED — pass an OGG/OPUS audio URL Telegram can fetch, OR a `file_id` from a prior /sendVoice for cheap re-sends. ' +
111
+ '(3) To synthesize from text, FIRST call the `synthesize_voice` tool — it returns a JSON `_file` ref; pass its `downloadUrl` string here as `voice`. NEVER pass the raw `_file` object; Telegram needs a string. ' +
112
+ '(4) Use this when the user sent voice and wants voice back, or when the operator framed the bot as voice-first. For text replies prefer send_telegram_message.',
113
+ parameters: [
114
+ p('chatId', 'Target chat id (number) or @channelusername.'),
115
+ p('voice', 'OGG/OPUS audio URL or Telegram file_id. Use the `downloadUrl` returned by synthesize_voice when chaining from text.'),
116
+ ],
117
+ },
118
+ {
119
+ operation: 'sendPoll',
120
+ label: 'Send poll',
121
+ toolName: 'send_telegram_poll',
122
+ group: 'Sending',
123
+ description: 'Send a poll to a chat. The options arg is an array of strings (2-10 options). For quiz-style polls (single correct answer), pass type="quiz" and a numeric correctOptionId. Polls require group/supergroup/channel chats — DMs reject them.',
124
+ parameters: [
125
+ p('chatId', 'Target chat id.'),
126
+ p('question', 'Poll question (max 300 chars).'),
127
+ p('options', 'Array of 2-10 answer strings. Pass as a JSON array: ["Option A","Option B"]. Each option max 100 chars.'),
128
+ p('isAnonymous', 'Optional. Default true. Set false to expose voter identities.', false, 'boolean'),
129
+ p('type', 'Optional. "regular" (default) or "quiz".', false),
130
+ p('correctOptionId', 'Required when type="quiz" — zero-indexed correct answer.', false, 'number'),
131
+ ],
132
+ },
133
+ {
134
+ operation: 'forwardMessage',
135
+ label: 'Forward message',
136
+ toolName: 'forward_telegram_message',
137
+ group: 'Sending',
138
+ description: 'Forward an existing message from one chat to another. Preserves the original sender attribution. Use for announcements, escalations, or copying a message between groups the bot is in.',
139
+ parameters: [
140
+ p('chatId', 'Destination chat id.'),
141
+ p('fromChatId', 'Source chat id.'),
142
+ p('messageId', 'Source message id.', true, 'number'),
143
+ ],
144
+ },
145
+ {
146
+ operation: 'editMessageText',
147
+ label: 'Edit message text',
148
+ toolName: 'edit_telegram_message',
149
+ group: 'Modify / acknowledge',
150
+ description: 'Replace the text of a previously-sent message. Use for progress updates, fixing typos, removing inline buttons after a press. The bot can only edit messages it sent within the last 48h.',
151
+ parameters: [
152
+ p('chatId', 'Chat id of the message.'),
153
+ p('messageId', 'Message id to edit (returned from a prior sendMessage).', true, 'number'),
154
+ p('text', 'New text for the message.'),
155
+ p('parseMode', 'Optional parse mode.', false),
156
+ ],
157
+ },
158
+ {
159
+ operation: 'deleteMessage',
160
+ label: 'Delete message',
161
+ toolName: 'delete_telegram_message',
162
+ group: 'Modify / acknowledge',
163
+ description: 'Delete a message. Bots can delete their own messages anytime; messages by users only when the bot has Delete Messages admin permission in that chat. Within 48h for bot-own messages in private chats.',
164
+ parameters: [
165
+ p('chatId', 'Chat id where the message lives.'),
166
+ p('messageId', 'Message id to delete.', true, 'number'),
167
+ ],
168
+ },
169
+ {
170
+ operation: 'answerCallbackQuery',
171
+ label: 'Answer callback query',
172
+ toolName: 'answer_telegram_callback',
173
+ group: 'Modify / acknowledge',
174
+ description: 'REQUIRED after a callback_query trigger fires (an inline-keyboard button press) — without this, the user sees a forever-spinner on the button. The text optionally shows as a toast or alert above the chat.',
175
+ parameters: [
176
+ p('callbackQueryId', 'The callback_query.id from the trigger payload — usually `{{payload.callbackQueryId}}`.'),
177
+ p('text', 'Optional toast/alert text shown to the user (max 200 chars).', false),
178
+ p('showAlert', 'Optional. true = modal alert; false (default) = toast notification.', false, 'boolean'),
179
+ ],
180
+ },
181
+ ];
182
+ /** Las herramientas por nombre, para despachar una llamada del modelo. */
183
+ export const TELEGRAM_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(TELEGRAM_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -0,0 +1,149 @@
1
+ /** All valid node type identifiers */
2
+ export type NodeType = 'webhook' | 'scheduledWorkflow' | 'chatTrigger' | 'trigger' | 'voiceAgent' | 'filter' | 'transform' | 'schemaValidator' | 'conditional' | 'delay' | 'rateLimiter' | 'aggregator' | 'cache' | 'code' | 'ai' | 'merge' | 'approval' | 'split' | 'loop' | 'markdown' | 'fileTransform' | 'limit' | 'router' | 'emailAction' | 'gmailAction' | 'httpAction' | 'mongoAction' | 'postgresAction' | 'notificationAction' | 'sheetsAction' | 'calendarAction' | 'docsAction' | 'driveAction' | 'firecrawlAction' | 'telegramAction' | 'whatsappAction' | 'discordAction' | 'slackAction' | 'googleContactsAction' | 'googleAnalyticsAction' | 'notionAction' | 'vectorStore' | 'rssAction' | 'socialMediaAction' | 'mailchimpAction' | 'shopifyAction' | 'githubAction' | 'jiraAction' | 'stickyNote';
3
+ /** Node role in the pipeline */
4
+ export type NodeRole = 'source' | 'processing' | 'flowControl' | 'routing' | 'action' | 'monitoring';
5
+ /** Handle positions on the canvas node */
6
+ export type HandlePosition = 'left' | 'right' | 'top' | 'bottom';
7
+ /** Connection rules — which node types can connect to/from this node */
8
+ export interface NodeConnectionConfig {
9
+ acceptsInputFrom: NodeType[];
10
+ canOutputTo: NodeType[];
11
+ special?: {
12
+ hasRejectionOutputNodes?: boolean;
13
+ hasBranches?: boolean;
14
+ isTerminal?: boolean;
15
+ hasLoopBack?: boolean;
16
+ };
17
+ }
18
+ /** UI-specific config for the dashboard canvas */
19
+ export interface NodeUIConfig {
20
+ fromNodes: boolean;
21
+ toNodes: boolean;
22
+ isSource?: boolean;
23
+ inputHandles?: HandlePosition[];
24
+ outputHandles?: HandlePosition[];
25
+ dotHandles?: {
26
+ input?: string[];
27
+ output?: string[];
28
+ };
29
+ /**
30
+ * When true, the canvas derives output handles from the node entity
31
+ * at render time (e.g. one handle per `entity.tools[i].name`) rather
32
+ * than from a static `dotHandles.output` list. Used by Voice Agent —
33
+ * the count of tools is data, not config. NodeShell branches on this
34
+ * flag to render the dynamic handle set.
35
+ */
36
+ dynamicOutputs?: boolean;
37
+ special?: {
38
+ hasBranches?: boolean;
39
+ isTerminal?: boolean;
40
+ hasLoopBack?: boolean;
41
+ connectModal?: 'branch' | 'rule' | 'output-group';
42
+ routerTargetLabel?: string;
43
+ /**
44
+ * Generic named output groups — used by connectModal: 'output-group'.
45
+ * The modal reads entity[outputGroupsField] as an array of groups,
46
+ * each with a name (outputGroupNameField), a summary (outputGroupSummaryField),
47
+ * and its own outputNodes[].
48
+ * Works for Split (outputs[]), Conditional (branches[]), or any future branching node.
49
+ */
50
+ outputGroups?: {
51
+ /** Entity field containing the array of groups (e.g. 'outputs', 'branches') */
52
+ field?: string;
53
+ /** Field within each group for display name (e.g. 'name') */
54
+ nameField?: string;
55
+ /** Field within each group for summary/description (e.g. 'field', 'filterMode') */
56
+ summaryField?: string;
57
+ /** Label shown in the modal (e.g. 'output', 'branch') */
58
+ label: string;
59
+ /** Entity field for the fallback/else path (e.g. 'elseOutputNodes') */
60
+ elseField?: string;
61
+ /**
62
+ * Fixed output groups — for nodes with a static set of outputs (e.g. approval: approve/reject).
63
+ * Each entry maps to a top-level entity field that holds outputNodes[].
64
+ * When set, `field`/`nameField` are ignored — modal shows these fixed options.
65
+ */
66
+ fixedGroups?: Array<{
67
+ name: string;
68
+ field: string;
69
+ }>;
70
+ /**
71
+ * Optional content-aware gate — the output-group modal only opens
72
+ * when `entity[gate.field] === gate.value`. Lets a node opt into
73
+ * multi-branch routing only in some modes (e.g. AI Node in
74
+ * classifier mode) without trapping every connection in the
75
+ * picker the rest of the time. When the gate fails, the
76
+ * FlowCanvas falls through to the universal
77
+ * `entity.outputNodes` connect path.
78
+ */
79
+ gate?: {
80
+ field: string;
81
+ value: unknown;
82
+ };
83
+ };
84
+ /**
85
+ * Maps output fields to alternate payload fields on the source entity.
86
+ * When a downstream node is connected via a specific output field,
87
+ * InputSection reads the alternate payload instead of lastPayload.
88
+ * Example: { doneOutputNodes: 'lastDonePayload' } for loop nodes.
89
+ */
90
+ alternatePayloads?: Record<string, string>;
91
+ /** Whether this node can be connected as a loop-back target. Only nodes that go through the unified node-lifecycle support loop-back detection. */
92
+ loopBackAllowed?: boolean;
93
+ };
94
+ }
95
+ /**
96
+ * How the event pipeline routes a payload to this node when an
97
+ * upstream webhook connects to it directly. Required so adding a
98
+ * new node type at compile time is impossible without picking one.
99
+ *
100
+ * - 'generic': dispatchConnectedNodes loops findActiveByInputEndpoint
101
+ * on the service and dispatches every active match. Use for
102
+ * pipeline-style nodes that ingest webhook payloads (filter,
103
+ * transform, ai, code, markdown, fileTransform, vectorStore).
104
+ *
105
+ * - 'custom': has a dedicated `case` in the dispatchOutputNodes
106
+ * switch that calls the node's specific service method. Used
107
+ * when the node needs scheduling, branching, gating, or queueing
108
+ * (delay, cache, aggregator, rateLimiter, conditional, split,
109
+ * loop, merge, approval, schemaValidator, router). The generic
110
+ * loop must NOT process these — would dispatch twice.
111
+ *
112
+ * - 'excluded': not dispatched in the webhook flow at all. Action
113
+ * nodes (email/http/mongo/notification/sheets/calendar/docs/
114
+ * firecrawl) are fired post-delivery in onDeliveryResult with
115
+ * their own loops; ingress nodes (webhook/scheduledWorkflow/
116
+ * chatTrigger/trigger) start the flow themselves;
117
+ * stickyNote is purely visual.
118
+ */
119
+ export type PipelineDispatchKind = 'generic' | 'custom' | 'excluded';
120
+ /** API dispatch config — how the event pipeline processes each node */
121
+ export interface NodeDispatchConfig {
122
+ service: string;
123
+ collection: string;
124
+ hasFilters: boolean;
125
+ outputFields: string[];
126
+ /** @deprecated Use `pipelineDispatch` instead. Kept for backwards compat
127
+ * during the migration; will be removed once every consumer reads
128
+ * `pipelineDispatch`. */
129
+ customDispatch: boolean;
130
+ /** Required. See PipelineDispatchKind for the contract per value. */
131
+ pipelineDispatch: PipelineDispatchKind;
132
+ }
133
+ /** Standardized payload metadata — every node output MUST include _meta */
134
+ export interface PayloadMeta {
135
+ iterable: boolean;
136
+ count: number;
137
+ iterateField?: string;
138
+ }
139
+ /** Helper to create _meta for single-object outputs */
140
+ export declare function singleMeta(): PayloadMeta;
141
+ /** Helper to create _meta for iterable array outputs */
142
+ export declare function iterableMeta(field: string, count: number): PayloadMeta;
143
+ /** Canvas prefix → node type mapping */
144
+ export interface PrefixMapping {
145
+ prefix: string;
146
+ type: NodeType;
147
+ /** Canvas node type used in React Flow nodeTypes map (e.g. "cache-node", "email-action") */
148
+ canvasType: string;
149
+ }
@@ -0,0 +1,8 @@
1
+ /** Helper to create _meta for single-object outputs */
2
+ export function singleMeta() {
3
+ return { iterable: false, count: 1 };
4
+ }
5
+ /** Helper to create _meta for iterable array outputs */
6
+ export function iterableMeta(field, count) {
7
+ return { iterable: true, iterateField: field, count };
8
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Dashboard UI config — handle positions, dot visibility, canvas prefixes.
3
+ * Only imported by the Dashboard. Broker and API don't need this.
4
+ */
5
+ import type { NodeType, NodeUIConfig, PrefixMapping } from './types.js';
6
+ export declare const NODE_UI: Record<NodeType, NodeUIConfig>;
7
+ /** Canvas prefix → node type mapping (used by FlowCanvas to identify node type from XYFlow node ID) */
8
+ export declare const PREFIX_TO_TYPE: PrefixMapping[];
9
+ /** Resolve canvas node ID to { type, entityId } */
10
+ export declare function resolveNodeId(canvasId: string): {
11
+ type: NodeType;
12
+ entityId: string;
13
+ } | null;
package/dist/esm/ui.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Dashboard UI config — handle positions, dot visibility, canvas prefixes.
3
+ * Only imported by the Dashboard. Broker and API don't need this.
4
+ */
5
+ export const NODE_UI = {
6
+ // ── Source nodes ──
7
+ // Una fuente arranca el flujo: `fromNodes: false` significa que nada puede
8
+ // conectarse HACIA ella, y por eso su DTO en la API tampoco declara
9
+ // `inputNodes`. Expone un único punto de salida, `right-out`. El de abajo se
10
+ // retiró porque no aportaba una segunda semántica — sólo una segunda diana
11
+ // donde soltar aristas.
12
+ //
13
+ // Ya no queda ningún punto de abajo en todo el registro. Aquí ponía que el
14
+ // `router` conservaba el suyo «porque ahí abajo significa por regla», y era
15
+ // mentira: el modal de reglas lo abre `special.connectModal`, que mira el
16
+ // TIPO del nodo origen y no el handle por el que sueltas, así que desde la
17
+ // derecha salía el mismo modal. El destino de una regla vive en
18
+ // `port: rule:<id>`; el handle es sólo por dónde entra y sale la línea.
19
+ webhook: { fromNodes: true, toNodes: true, isSource: true },
20
+ scheduledWorkflow: { fromNodes: false, toNodes: true, isSource: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] } },
21
+ chatTrigger: { fromNodes: false, toNodes: true, isSource: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] } },
22
+ trigger: { fromNodes: false, toNodes: true, isSource: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] } },
23
+ // Voice Agent: dynamicOutputs=true tells NodeShell to derive ONE
24
+ // output handle per non-disabled `entity.tools[i].name`. The
25
+ // `right-out` fallback handle is rendered when the agent has zero
26
+ // tools so the user has somewhere to drop the first wired connection
27
+ // before any tools are defined (rare but possible during onboarding).
28
+ voiceAgent: { fromNodes: false, toNodes: true, isSource: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] }, dynamicOutputs: true },
29
+ // ── Pipeline processing nodes ──
30
+ filter: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
31
+ transform: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
32
+ // One output only: a payload that fails validation stops at the node.
33
+ schemaValidator: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { loopBackAllowed: true } },
34
+ conditional: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { loopBackAllowed: true, connectModal: 'output-group', outputGroups: { field: 'branches', nameField: 'name', summaryField: 'filterMode', label: 'branch', elseField: 'elseOutputNodes' } } },
35
+ delay: { fromNodes: true, toNodes: true },
36
+ rateLimiter: { fromNodes: true, toNodes: true },
37
+ aggregator: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
38
+ cache: { fromNodes: true, toNodes: true },
39
+ code: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
40
+ // AI Node: in classifier mode (entity.outputMode === 'classifier') the
41
+ // node fans out to N output handles — one per category in
42
+ // `entity.categories[]`, with the wiring in `categories[].outputNodes`.
43
+ // An item the classifier cannot place lands on the synthetic "other"
44
+ // value (see `entity.includeOtherBranch`) and stops there — "other" has
45
+ // no handle and no route. In any other outputMode the node falls back to
46
+ // the single right-out handle NodeShell renders when no dynamic outputs
47
+ // materialize.
48
+ ai: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { loopBackAllowed: true, connectModal: 'output-group', outputGroups: { field: 'categories', nameField: 'name', summaryField: 'description', label: 'category', gate: { field: 'outputMode', value: 'classifier' } } } },
49
+ markdown: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
50
+ fileTransform: { fromNodes: true, toNodes: true },
51
+ limit: { fromNodes: true, toNodes: true, special: { loopBackAllowed: true } },
52
+ // ── Flow control nodes ──
53
+ split: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { connectModal: 'output-group', outputGroups: { field: 'outputs', nameField: 'name', summaryField: 'field', label: 'output' } } },
54
+ merge: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'] },
55
+ approval: { fromNodes: true, toNodes: true, outputHandles: ['right'], special: { connectModal: 'output-group', outputGroups: { fixedGroups: [{ name: 'Approve', field: 'outputNodes' }, { name: 'Reject', field: 'rejectionOutputNodes' }], label: 'action' } } },
56
+ loop: { fromNodes: true, toNodes: true, inputHandles: ['left'], outputHandles: ['right'], dotHandles: { input: ['left-in'], output: ['right-out-loop', 'right-in-loopback', 'right-out-done'] }, special: { hasLoopBack: true, alternatePayloads: { doneOutputNodes: 'lastDonePayload' } } },
57
+ // ── Routing nodes ──
58
+ router: { fromNodes: true, toNodes: true, outputHandles: ['right'], dotHandles: { output: ['right-out'] }, special: { connectModal: 'rule' } },
59
+ // ── Action nodes ──
60
+ emailAction: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'], outputHandles: ['right'], dotHandles: { input: ['left-in', 'top-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'email' } },
61
+ gmailAction: { fromNodes: true, toNodes: true, inputHandles: ['left', 'top'], outputHandles: ['right'], dotHandles: { input: ['left-in', 'top-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'gmail' } },
62
+ httpAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
63
+ mongoAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
64
+ postgresAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
65
+ notificationAction: { fromNodes: true, toNodes: false, inputHandles: ['left', 'top'], dotHandles: { input: ['left-in', 'top-in'] }, special: { isTerminal: true, routerTargetLabel: 'notification' } },
66
+ sheetsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
67
+ calendarAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
68
+ docsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
69
+ driveAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
70
+ firecrawlAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
71
+ telegramAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'telegram' } },
72
+ whatsappAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'whatsapp' } },
73
+ discordAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'discord' } },
74
+ mailchimpAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'mailchimp' } },
75
+ shopifyAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'shopify' } },
76
+ githubAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'github' } },
77
+ jiraAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'jira' } },
78
+ slackAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'slack' } },
79
+ googleContactsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
80
+ googleAnalyticsAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
81
+ notionAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true, routerTargetLabel: 'notion' } },
82
+ vectorStore: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
83
+ rssAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
84
+ socialMediaAction: { fromNodes: true, toNodes: true, inputHandles: ['left'], dotHandles: { input: ['left-in'], output: ['right-out'] }, special: { loopBackAllowed: true } },
85
+ // ── Annotations ──
86
+ stickyNote: { fromNodes: false, toNodes: false },
87
+ };
88
+ /** Canvas prefix → node type mapping (used by FlowCanvas to identify node type from XYFlow node ID) */
89
+ export const PREFIX_TO_TYPE = [
90
+ { prefix: 'wh-', type: 'webhook', canvasType: 'webhook' },
91
+ { prefix: 'swf-', type: 'scheduledWorkflow', canvasType: 'scheduledWorkflow' },
92
+ { prefix: 'chat-', type: 'chatTrigger', canvasType: 'chatTrigger' },
93
+ { prefix: 'trg-', type: 'trigger', canvasType: 'trigger' },
94
+ { prefix: 'va-', type: 'voiceAgent', canvasType: 'voiceAgent' },
95
+ { prefix: 'ea-', type: 'emailAction', canvasType: 'emailAction' },
96
+ { prefix: 'gm-', type: 'gmailAction', canvasType: 'gmailAction' },
97
+ { prefix: 'tg-', type: 'telegramAction', canvasType: 'telegramAction' },
98
+ { prefix: 'wa-', type: 'whatsappAction', canvasType: 'whatsappAction' },
99
+ { prefix: 'dc-', type: 'discordAction', canvasType: 'discordAction' },
100
+ { prefix: 'sl-', type: 'slackAction', canvasType: 'slackAction' },
101
+ { prefix: 'mc-', type: 'mailchimpAction', canvasType: 'mailchimpAction' },
102
+ { prefix: 'shp-', type: 'shopifyAction', canvasType: 'shopifyAction' },
103
+ { prefix: 'gh-', type: 'githubAction', canvasType: 'githubAction' },
104
+ { prefix: 'jr-', type: 'jiraAction', canvasType: 'jiraAction' },
105
+ { prefix: 'gc-', type: 'googleContactsAction', canvasType: 'googleContactsAction' },
106
+ { prefix: 'gaa-', type: 'googleAnalyticsAction', canvasType: 'googleAnalyticsAction' },
107
+ { prefix: 'ntn-', type: 'notionAction', canvasType: 'notionAction' },
108
+ { prefix: 'ha-', type: 'httpAction', canvasType: 'httpAction' },
109
+ { prefix: 'ma-', type: 'mongoAction', canvasType: 'mongoAction' },
110
+ { prefix: 'pg-', type: 'postgresAction', canvasType: 'postgresAction' },
111
+ { prefix: 'na-', type: 'notificationAction', canvasType: 'notificationAction' },
112
+ { prefix: 'ro-', type: 'router', canvasType: 'router' },
113
+ { prefix: 'fi-', type: 'filter', canvasType: 'filter' },
114
+ { prefix: 'tr-', type: 'transform', canvasType: 'transform' },
115
+ { prefix: 'sv-', type: 'schemaValidator', canvasType: 'schemaValidator' },
116
+ { prefix: 'delay-', type: 'delay', canvasType: 'delay' },
117
+ { prefix: 'sheet-', type: 'sheetsAction', canvasType: 'sheetsAction' },
118
+ { prefix: 'cal-', type: 'calendarAction', canvasType: 'calendarAction' },
119
+ { prefix: 'appr-', type: 'approval', canvasType: 'approval' },
120
+ { prefix: 'merge-', type: 'merge', canvasType: 'merge' },
121
+ { prefix: 'cond-', type: 'conditional', canvasType: 'conditional' },
122
+ { prefix: 'rl-', type: 'rateLimiter', canvasType: 'rateLimiter' },
123
+ { prefix: 'agg-', type: 'aggregator', canvasType: 'aggregator' },
124
+ { prefix: 'cache-', type: 'cache', canvasType: 'cache' },
125
+ { prefix: 'code-', type: 'code', canvasType: 'code' },
126
+ { prefix: 'ai-', type: 'ai', canvasType: 'ai' },
127
+ { prefix: 'split-', type: 'split', canvasType: 'split' },
128
+ { prefix: 'loop-', type: 'loop', canvasType: 'loop' },
129
+ { prefix: 'md-', type: 'markdown', canvasType: 'markdown' },
130
+ { prefix: 'ft-', type: 'fileTransform', canvasType: 'fileTransform' },
131
+ { prefix: 'lim-', type: 'limit', canvasType: 'limit' },
132
+ { prefix: 'doc-', type: 'docsAction', canvasType: 'docsAction' },
133
+ { prefix: 'drv-', type: 'driveAction', canvasType: 'driveAction' },
134
+ { prefix: 'fc-', type: 'firecrawlAction', canvasType: 'firecrawlAction' },
135
+ { prefix: 'vs-', type: 'vectorStore', canvasType: 'vectorStore' },
136
+ { prefix: 'rssa-', type: 'rssAction', canvasType: 'rssAction' },
137
+ { prefix: 'sm-', type: 'socialMediaAction', canvasType: 'socialMediaAction' },
138
+ { prefix: 'note-', type: 'stickyNote', canvasType: 'stickyNote' },
139
+ ];
140
+ /** Resolve canvas node ID to { type, entityId } */
141
+ export function resolveNodeId(canvasId) {
142
+ // Sort by prefix length descending so longer prefixes match first
143
+ const sorted = [...PREFIX_TO_TYPE].sort((a, b) => b.prefix.length - a.prefix.length);
144
+ for (const { prefix, type } of sorted) {
145
+ if (canvasId.startsWith(prefix)) {
146
+ return { type, entityId: canvasId.slice(prefix.length) };
147
+ }
148
+ }
149
+ return null;
150
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * WhatsApp Business Cloud API operation enum — single source of truth
3
+ * across the API, Dashboard, and any future consumers (Message Broker,
4
+ * MCP server). Used by:
5
+ * - whatsappAction entity / DTO (api): operation field + validation
6
+ * - dashboard WhatsAppAction operation picker
7
+ * - MCP toolkit-specs expansion (one aiEnabled whatsappAction → N tools)
8
+ *
9
+ * Targets Meta Cloud API v21+ (the only supported variant after the
10
+ * On-Premises sunset on 2025-10-23). Each op maps to a single POST to
11
+ * `https://graph.facebook.com/v21.0/<phone_number_id>/messages` with
12
+ * a different `type` discriminator in the body.
13
+ *
14
+ * Phase 1 covers the ten ops that handle ~95% of business messaging.
15
+ * Phase 2 candidates (sendInteractiveButtons, sendInteractiveList,
16
+ * sendSticker, sendContacts) extend this list — bump the package and
17
+ * re-install in consumers, same pattern as Gmail / Calendar / Drive
18
+ * / Telegram.
19
+ */
20
+ export declare const WHATSAPP_OPERATIONS: readonly ["sendText", "sendImage", "sendAudio", "sendVideo", "sendDocument", "sendLocation", "sendTemplate", "replyToMessage", "markAsRead", "sendReaction"];
21
+ export type WhatsAppOperation = (typeof WHATSAPP_OPERATIONS)[number];
22
+ /** Type guard — useful when validating untrusted input (DTOs, tool calls). */
23
+ export declare function isWhatsAppOperation(value: unknown): value is WhatsAppOperation;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WhatsApp Business Cloud API operation enum — single source of truth
3
+ * across the API, Dashboard, and any future consumers (Message Broker,
4
+ * MCP server). Used by:
5
+ * - whatsappAction entity / DTO (api): operation field + validation
6
+ * - dashboard WhatsAppAction operation picker
7
+ * - MCP toolkit-specs expansion (one aiEnabled whatsappAction → N tools)
8
+ *
9
+ * Targets Meta Cloud API v21+ (the only supported variant after the
10
+ * On-Premises sunset on 2025-10-23). Each op maps to a single POST to
11
+ * `https://graph.facebook.com/v21.0/<phone_number_id>/messages` with
12
+ * a different `type` discriminator in the body.
13
+ *
14
+ * Phase 1 covers the ten ops that handle ~95% of business messaging.
15
+ * Phase 2 candidates (sendInteractiveButtons, sendInteractiveList,
16
+ * sendSticker, sendContacts) extend this list — bump the package and
17
+ * re-install in consumers, same pattern as Gmail / Calendar / Drive
18
+ * / Telegram.
19
+ */
20
+ export const WHATSAPP_OPERATIONS = [
21
+ 'sendText',
22
+ 'sendImage',
23
+ 'sendAudio',
24
+ 'sendVideo',
25
+ 'sendDocument',
26
+ 'sendLocation',
27
+ 'sendTemplate',
28
+ 'replyToMessage',
29
+ 'markAsRead',
30
+ 'sendReaction',
31
+ ];
32
+ /** Type guard — useful when validating untrusted input (DTOs, tool calls). */
33
+ export function isWhatsAppOperation(value) {
34
+ return (typeof value === 'string' &&
35
+ WHATSAPP_OPERATIONS.includes(value));
36
+ }