@hostwebhook/node-types 1.86.0 → 1.88.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 (59) hide show
  1. package/dist/calendar-toolkit.d.ts +3 -0
  2. package/dist/calendar-toolkit.js +3 -2
  3. package/dist/clase-de-herramienta.d.ts +61 -0
  4. package/dist/clase-de-herramienta.js +153 -0
  5. package/dist/discord-toolkit.d.ts +2 -3
  6. package/dist/discord-toolkit.js +3 -6
  7. package/dist/docs-toolkit.d.ts +2 -1
  8. package/dist/docs-toolkit.js +3 -6
  9. package/dist/drive-toolkit.d.ts +9 -4
  10. package/dist/drive-toolkit.js +3 -4
  11. package/dist/esm/calendar-toolkit.d.ts +3 -0
  12. package/dist/esm/calendar-toolkit.js +3 -2
  13. package/dist/esm/clase-de-herramienta.d.ts +61 -0
  14. package/dist/esm/clase-de-herramienta.js +148 -0
  15. package/dist/esm/discord-toolkit.d.ts +2 -3
  16. package/dist/esm/discord-toolkit.js +3 -6
  17. package/dist/esm/docs-toolkit.d.ts +2 -1
  18. package/dist/esm/docs-toolkit.js +3 -6
  19. package/dist/esm/drive-toolkit.d.ts +9 -4
  20. package/dist/esm/drive-toolkit.js +3 -4
  21. package/dist/esm/gmail-operations.d.ts +2 -0
  22. package/dist/esm/gmail-operations.js +7 -6
  23. package/dist/esm/google-contacts-toolkit.d.ts +46 -0
  24. package/dist/esm/google-contacts-toolkit.js +180 -0
  25. package/dist/esm/index.d.ts +10 -0
  26. package/dist/esm/index.js +9 -0
  27. package/dist/esm/mongo-toolkit.d.ts +47 -0
  28. package/dist/esm/mongo-toolkit.js +98 -0
  29. package/dist/esm/postgres-toolkit.d.ts +52 -0
  30. package/dist/esm/postgres-toolkit.js +85 -0
  31. package/dist/esm/registry.js +10 -0
  32. package/dist/esm/sheets-toolkit.d.ts +2 -1
  33. package/dist/esm/sheets-toolkit.js +3 -6
  34. package/dist/esm/slack-toolkit.d.ts +2 -3
  35. package/dist/esm/slack-toolkit.js +3 -4
  36. package/dist/esm/telegram-toolkit.d.ts +3 -0
  37. package/dist/esm/telegram-toolkit.js +3 -2
  38. package/dist/esm/toolkits.d.ts +111 -0
  39. package/dist/esm/toolkits.js +178 -0
  40. package/dist/gmail-operations.d.ts +2 -0
  41. package/dist/gmail-operations.js +11 -10
  42. package/dist/google-contacts-toolkit.d.ts +46 -0
  43. package/dist/google-contacts-toolkit.js +183 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +32 -1
  46. package/dist/mongo-toolkit.d.ts +47 -0
  47. package/dist/mongo-toolkit.js +101 -0
  48. package/dist/postgres-toolkit.d.ts +52 -0
  49. package/dist/postgres-toolkit.js +88 -0
  50. package/dist/registry.js +10 -0
  51. package/dist/sheets-toolkit.d.ts +2 -1
  52. package/dist/sheets-toolkit.js +3 -6
  53. package/dist/slack-toolkit.d.ts +2 -3
  54. package/dist/slack-toolkit.js +3 -4
  55. package/dist/telegram-toolkit.d.ts +3 -0
  56. package/dist/telegram-toolkit.js +3 -2
  57. package/dist/toolkits.d.ts +111 -0
  58. package/dist/toolkits.js +190 -0
  59. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PostgreSQL AI Toolkit — las herramientas que un postgresAction expone cuando
3
+ * `aiEnabled` está encendido.
4
+ *
5
+ * ## Por qué las escrituras NO reciben SQL (2026-09-24, decisión de Ariel)
6
+ *
7
+ * El nodo, en modo de una operación, ejecuta el SQL que se le escribe: sus
8
+ * cinco operaciones son una etiqueta encima del mismo SQL crudo. Si el toolkit
9
+ * hiciera lo mismo, bloquear una herramienta no bloquearía nada — un
10
+ * `insert_rows` puede llevar un `DELETE` dentro de un `WITH`, y quien bloqueó
11
+ * `delete_rows` seguiría viendo desaparecer filas.
12
+ *
13
+ * Así que las tres escrituras reciben la tabla del nodo y JSON, y el SQL lo
14
+ * genera el handler, parametrizado y con los identificadores entrecomillados:
15
+ * el modelo no escribe SQL que se ejecute como escritura.
16
+ *
17
+ * ## Lo que queda con SQL, y cómo se contiene
18
+ *
19
+ * - `query_rows` recibe SQL, y corre dentro de una transacción **READ ONLY**:
20
+ * un `DELETE`, un `UPDATE` o un `CREATE` los rechaza el propio Postgres, no
21
+ * una expresión regular nuestra. Puede leer otras tablas —un JOIN hace
22
+ * falta a menudo—; el límite ahí lo pone el usuario de base de datos de la
23
+ * credencial.
24
+ * - `execute_sql` recibe SQL cualquiera, DDL incluido. Es la excepción
25
+ * consciente a «sólo la tabla del nodo»: SQL crudo no se puede vallar a una
26
+ * tabla. La contienen tres cosas: es siempre destructiva (el gate del AI
27
+ * Node la para si está encendido), se puede bloquear en el nodo, y el
28
+ * usuario de base de datos.
29
+ *
30
+ * Los textos van en inglés porque los lee el modelo.
31
+ */
32
+ import type { PostgresOperation } from './postgres-operations';
33
+ export interface PostgresToolkitParameter {
34
+ name: string;
35
+ /** El esquema MCP sólo admite estos tres; lo estructurado viaja como JSON
36
+ * en un `string`. */
37
+ type: 'string' | 'number' | 'boolean';
38
+ description: string;
39
+ required: boolean;
40
+ }
41
+ export interface PostgresToolkitSpec {
42
+ operation: PostgresOperation;
43
+ label: string;
44
+ toolName: string;
45
+ description: string;
46
+ parameters: PostgresToolkitParameter[];
47
+ /** Pisa o borra. Lo pone `marcarDestructivas`, no se escribe a mano. */
48
+ destructive?: boolean;
49
+ }
50
+ export declare const POSTGRES_TOOLKIT_SPECS: PostgresToolkitSpec[];
51
+ /** Las herramientas por nombre, para despachar una llamada del modelo. */
52
+ export declare const POSTGRES_TOOLKIT_BY_TOOL_NAME: Record<string, PostgresToolkitSpec>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * PostgreSQL AI Toolkit — las herramientas que un postgresAction expone cuando
3
+ * `aiEnabled` está encendido.
4
+ *
5
+ * ## Por qué las escrituras NO reciben SQL (2026-09-24, decisión de Ariel)
6
+ *
7
+ * El nodo, en modo de una operación, ejecuta el SQL que se le escribe: sus
8
+ * cinco operaciones son una etiqueta encima del mismo SQL crudo. Si el toolkit
9
+ * hiciera lo mismo, bloquear una herramienta no bloquearía nada — un
10
+ * `insert_rows` puede llevar un `DELETE` dentro de un `WITH`, y quien bloqueó
11
+ * `delete_rows` seguiría viendo desaparecer filas.
12
+ *
13
+ * Así que las tres escrituras reciben la tabla del nodo y JSON, y el SQL lo
14
+ * genera el handler, parametrizado y con los identificadores entrecomillados:
15
+ * el modelo no escribe SQL que se ejecute como escritura.
16
+ *
17
+ * ## Lo que queda con SQL, y cómo se contiene
18
+ *
19
+ * - `query_rows` recibe SQL, y corre dentro de una transacción **READ ONLY**:
20
+ * un `DELETE`, un `UPDATE` o un `CREATE` los rechaza el propio Postgres, no
21
+ * una expresión regular nuestra. Puede leer otras tablas —un JOIN hace
22
+ * falta a menudo—; el límite ahí lo pone el usuario de base de datos de la
23
+ * credencial.
24
+ * - `execute_sql` recibe SQL cualquiera, DDL incluido. Es la excepción
25
+ * consciente a «sólo la tabla del nodo»: SQL crudo no se puede vallar a una
26
+ * tabla. La contienen tres cosas: es siempre destructiva (el gate del AI
27
+ * Node la para si está encendido), se puede bloquear en el nodo, y el
28
+ * usuario de base de datos.
29
+ *
30
+ * Los textos van en inglés porque los lee el modelo.
31
+ */
32
+ import { marcarDestructivas } from './clase-de-herramienta.js';
33
+ const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
34
+ const donde = (que) => p('where', `JSON object selecting the rows ${que}: each key is a column and each value the value it must equal, all of them at once, e.g. {"id": 42} or {"status": "pending", "customer_id": 7}. It cannot be empty — an empty condition would touch every row.`);
35
+ const parametros = () => p('params', 'Optional JSON array with the values for the $1, $2… placeholders in the SQL, e.g. ["paid", 100]. Always pass values this way instead of writing them into the SQL.', false);
36
+ export const POSTGRES_TOOLKIT_SPECS = marcarDestructivas([
37
+ {
38
+ operation: 'query',
39
+ label: 'Query rows',
40
+ toolName: 'query_rows',
41
+ description: 'Run a read-only SQL query (SELECT, WITH … SELECT) and return its rows, at most 200. It runs inside a READ ONLY transaction: a statement that writes is rejected by the database. Use it for lookups, counts, sums and joins.',
42
+ parameters: [
43
+ p('sql', 'The SELECT statement, with $1, $2… placeholders for values, e.g. SELECT * FROM orders WHERE status = $1 LIMIT 20.'),
44
+ parametros(),
45
+ ],
46
+ },
47
+ {
48
+ operation: 'insertOne',
49
+ label: 'Insert rows',
50
+ toolName: 'insert_rows',
51
+ description: "Insert one or more rows into this node's table and return them as stored, with the values the database filled in (ids, defaults). It does not check for duplicates.",
52
+ parameters: [
53
+ p('rows', 'JSON object with one row, or a JSON array of rows. Keys are column names, e.g. {"name": "Ariel", "email": "a@b.c"}. A key that is not a column makes the call fail.'),
54
+ ],
55
+ },
56
+ {
57
+ operation: 'update',
58
+ label: 'Update rows',
59
+ toolName: 'update_rows',
60
+ description: "Update the rows of this node's table that match a condition, overwriting the columns you set, and return them after the update. When no row matches it changes nothing.",
61
+ parameters: [
62
+ p('set', 'JSON object with the columns to overwrite and their new values, e.g. {"status": "shipped"}.'),
63
+ donde('to update'),
64
+ ],
65
+ },
66
+ {
67
+ operation: 'delete',
68
+ label: 'Delete rows',
69
+ toolName: 'delete_rows',
70
+ description: "Delete the rows of this node's table that match a condition and return them as they were. This cannot be undone: when you are not sure which rows match, check with query_rows first.",
71
+ parameters: [donde('to delete')],
72
+ },
73
+ {
74
+ operation: 'execute',
75
+ label: 'Execute SQL',
76
+ toolName: 'execute_sql',
77
+ description: 'Run any SQL statement — including ones that change the schema (CREATE, ALTER, DROP) — and return what it produces. It is not limited to this node\'s table. Prefer query_rows, insert_rows, update_rows or delete_rows whenever one of them can do the job.',
78
+ parameters: [
79
+ p('sql', 'The SQL statement, with $1, $2… placeholders for values.'),
80
+ parametros(),
81
+ ],
82
+ },
83
+ ]);
84
+ /** Las herramientas por nombre, para despachar una llamada del modelo. */
85
+ export const POSTGRES_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(POSTGRES_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -401,6 +401,11 @@ export const NODE_REGISTRY = {
401
401
  allStateKey: "allMongoActions",
402
402
  color: "#34d399",
403
403
  testable: true,
404
+ /* Modo AI toolkit (2026-09-24): con `aiEnabled` el nodo sólo lo llama un
405
+ AI Node —como el resto de toolkits—, y una arista del lienzo no le
406
+ llevaría nada. Hasta que la api y el dashboard le den `aiEnabled`, el
407
+ campo no existe y esto devuelve `false`: no cambia nada. */
408
+ isToolOnly: (e) => e.aiEnabled === true,
404
409
  },
405
410
  postgresAction: {
406
411
  type: "postgresAction",
@@ -413,6 +418,11 @@ export const NODE_REGISTRY = {
413
418
  allStateKey: "allPostgresActions",
414
419
  color: "#336791",
415
420
  testable: true,
421
+ /* Modo AI toolkit (2026-09-24): con `aiEnabled` el nodo sólo lo llama un
422
+ AI Node —como el resto de toolkits—, y una arista del lienzo no le
423
+ llevaría nada. Hasta que la api y el dashboard le den `aiEnabled`, el
424
+ campo no existe y esto devuelve `false`: no cambia nada. */
425
+ isToolOnly: (e) => e.aiEnabled === true,
416
426
  },
417
427
  notificationAction: {
418
428
  type: "notificationAction",
@@ -34,7 +34,8 @@ export interface SheetsToolkitSpec {
34
34
  toolName: string;
35
35
  description: string;
36
36
  parameters: SheetsToolkitParameter[];
37
- /** Escribe en la hoja del usuario. Lo usa la confirmación previa del AI Node. */
37
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
38
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
38
39
  destructive?: boolean;
39
40
  }
40
41
  export declare const SHEETS_TOOLKIT_SPECS: SheetsToolkitSpec[];
@@ -19,6 +19,7 @@
19
19
  * pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
20
20
  * y quien mire la lista de herramientas del servidor MCP.
21
21
  */
22
+ import { marcarDestructivas } from './clase-de-herramienta.js';
22
23
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
23
24
  /*
24
25
  * La hoja y la pestaña del nodo van como **defecto**, y el modelo puede
@@ -41,14 +42,13 @@ const p = (name, description, required = true, type = 'string') => ({ name, type
41
42
  const hoja = () => p('spreadsheetId', "Id of the Google Sheets document — the long chunk of its URL, between /d/ and /edit.");
42
43
  const pestana = () => p('sheetName', 'Name of the tab inside the document, exactly as it reads on its tab strip.');
43
44
  const fila = (que) => p('row', `JSON object holding ${que}. Keys are the sheet's **header names**, spelled exactly as they appear in its title row: {"Name": "Ariel", "Email": "a@b.c"}. A key that is not a header makes the call fail, so when you do not know the headers, call get_rows first.`);
44
- export const SHEETS_TOOLKIT_SPECS = [
45
+ export const SHEETS_TOOLKIT_SPECS = marcarDestructivas([
45
46
  {
46
47
  operation: 'appendRow',
47
48
  label: 'Append row',
48
49
  toolName: 'append_row',
49
50
  description: 'Add a new row at the bottom of the tab. It does not check whether the row already exists — when it might, use append_or_update_row instead so you do not duplicate it.',
50
51
  parameters: [fila('the values of the new row'), hoja(), pestana()],
51
- destructive: true,
52
52
  },
53
53
  {
54
54
  operation: 'updateRow',
@@ -62,7 +62,6 @@ export const SHEETS_TOOLKIT_SPECS = [
62
62
  hoja(),
63
63
  pestana(),
64
64
  ],
65
- destructive: true,
66
65
  },
67
66
  {
68
67
  operation: 'appendOrUpdateRow',
@@ -76,7 +75,6 @@ export const SHEETS_TOOLKIT_SPECS = [
76
75
  hoja(),
77
76
  pestana(),
78
77
  ],
79
- destructive: true,
80
78
  },
81
79
  {
82
80
  operation: 'readRange',
@@ -113,9 +111,8 @@ export const SHEETS_TOOLKIT_SPECS = [
113
111
  * en el nodo, y la api la aplica.
114
112
  */
115
113
  parameters: [p('title', 'Name of the new document.')],
116
- destructive: true,
117
114
  },
118
- ];
115
+ ]);
119
116
  /** Las herramientas por nombre, para despachar una llamada del modelo. */
120
117
  export const SHEETS_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(SHEETS_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
121
118
  /**
@@ -38,9 +38,8 @@ export interface SlackToolkitSpec {
38
38
  /** Descripción (más reglas de uso) que ve el LLM. */
39
39
  description: string;
40
40
  parameters: SlackToolkitParameter[];
41
- /** Operaciones irreversibles o que cambian privilegios. La capa MCP las
42
- * bloquea cuando el nodo de IA lleva `requireConfirmationForDestructive`,
43
- * salvo que la llamada traiga confirmación explícita. */
41
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
42
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
44
43
  destructive?: boolean;
45
44
  }
46
45
  export declare const SLACK_TOOLKIT_SPECS: SlackToolkitSpec[];
@@ -23,8 +23,9 @@
23
23
  * y quien mire la lista de herramientas del servidor MCP.
24
24
  */
25
25
  import { camposDeSlackNoDisponibles, operacionesDeSlackPara, } from './slack-operations.js';
26
+ import { marcarDestructivas } from './clase-de-herramienta.js';
26
27
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
27
- export const SLACK_TOOLKIT_SPECS = [
28
+ export const SLACK_TOOLKIT_SPECS = marcarDestructivas([
28
29
  // ── Messages ─────────────────────────────────────────────────────
29
30
  {
30
31
  operation: 'sendMessage',
@@ -63,7 +64,6 @@ export const SLACK_TOOLKIT_SPECS = [
63
64
  p('channel', 'Channel where the message lives.'),
64
65
  p('ts', 'Timestamp of the message to delete.'),
65
66
  ],
66
- destructive: true,
67
67
  },
68
68
  {
69
69
  operation: 'sendEphemeral',
@@ -142,7 +142,6 @@ export const SLACK_TOOLKIT_SPECS = [
142
142
  p('name', 'Channel name — lowercase, no spaces, max 80 chars.'),
143
143
  p('isPrivate', 'Create a private channel instead of public. Default false.', false, 'boolean'),
144
144
  ],
145
- destructive: true,
146
145
  },
147
146
  {
148
147
  operation: 'inviteToChannel',
@@ -212,7 +211,7 @@ export const SLACK_TOOLKIT_SPECS = [
212
211
  p('threadTs', 'Thread to attach the file to.', false),
213
212
  ],
214
213
  },
215
- ];
214
+ ]);
216
215
  export const SLACK_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(SLACK_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
217
216
  /**
218
217
  * Las herramientas que ofrecerle al modelo con ESTA credencial.
@@ -39,6 +39,9 @@ export interface TelegramToolkitSpec {
39
39
  /** Descripción (más reglas de uso) que ve el LLM. */
40
40
  description: string;
41
41
  parameters: TelegramToolkitParameter[];
42
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
43
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
44
+ destructive?: boolean;
42
45
  /**
43
46
  * Encabezado de grupo para la lista de la página de detalle. Las filas
44
47
  * consecutivas que comparten grupo se pintan bajo un único título, así que el
@@ -19,6 +19,7 @@
19
19
  * usarse sin adaptador. Los textos van en inglés porque los lee el modelo y
20
20
  * quien mire la lista de herramientas del servidor MCP.
21
21
  */
22
+ import { marcarDestructivas } from './clase-de-herramienta.js';
22
23
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
23
24
  /*
24
25
  * `sendAndWaitForResponse` NO está aquí, y es una decisión, no un olvido: la op
@@ -33,7 +34,7 @@ const p = (name, description, required = true, type = 'string') => ({ name, type
33
34
  * campo en ninguna de las dos capas, y añadirlo cambiaría cuándo el AI Node pide
34
35
  * confirmación. Eso es otra decisión, no parte de mudar el esquema de sitio.
35
36
  */
36
- export const TELEGRAM_TOOLKIT_SPECS = [
37
+ export const TELEGRAM_TOOLKIT_SPECS = marcarDestructivas([
37
38
  {
38
39
  operation: 'sendMessage',
39
40
  label: 'Send message',
@@ -178,6 +179,6 @@ export const TELEGRAM_TOOLKIT_SPECS = [
178
179
  p('showAlert', 'Optional. true = modal alert; false (default) = toast notification.', false, 'boolean'),
179
180
  ],
180
181
  },
181
- ];
182
+ ]);
182
183
  /** Las herramientas por nombre, para despachar una llamada del modelo. */
183
184
  export const TELEGRAM_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(TELEGRAM_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Los toolkits por tipo de nodo: qué herramientas expone cada nodo en modo
3
+ * AI toolkit, en un solo sitio.
4
+ *
5
+ * ## Para qué (2026-09-24)
6
+ *
7
+ * Para que el gate «Block destructive tool calls» del AI Node pueda preguntar
8
+ * por la OPERACIÓN y no sólo por el nombre. Hasta hoy sólo miraba el nombre
9
+ * contra unos patrones (`delete_*`, `drop_*`…), así que `update_row` de Sheets
10
+ * —que pisa una fila— pasaba con el gate encendido. Con esto, la herramienta
11
+ * que el AI Node guardó al conectar (`nodeRefType` + `operationOverride`) se
12
+ * resuelve aquí a su operación, y su `destructive` —que sale del verbo, ver
13
+ * `clase-de-herramienta.ts`— decide.
14
+ *
15
+ * ## Lo que falta aquí, a propósito
16
+ *
17
+ * WhatsApp y Social siguen con sus specs fuera de este paquete (WhatsApp
18
+ * escrito a mano en la api y el dashboard; Social armado por entidad desde el
19
+ * registro de proveedores). Quien consulte este registro tiene que componerlos
20
+ * por su lado — {@link esOperacionDestructiva} devuelve `null` para ellos, que
21
+ * significa «no lo sé», no «no es destructiva». Contacts se mudó el
22
+ * 2026-09-24.
23
+ *
24
+ * ## El bloqueo por nodo y la puerta sin herramienta (2026-09-24)
25
+ *
26
+ * Abajo viven también las dos reglas que se aplican a TODA llamada a un nodo en
27
+ * modo toolkit, en un solo sitio para que la api, hw-nodes, node-sdk y el
28
+ * dashboard pregunten lo mismo:
29
+ *
30
+ * - «Block these tool calls»: el dueño bloquea herramientas en el nodo
31
+ * (`blockedTools`, por nombre). Vale para todo tipo de este registro
32
+ * ({@link admiteBloqueo}).
33
+ * - Sin herramienta no se corre: en modo toolkit un nodo sólo ejecuta la
34
+ * herramienta que le nombran en `operation`. Un evento sin ella —una arista
35
+ * vieja del lienzo— ya no corre la operación de «Single operation», que en
36
+ * ese modo no se ve. Decisión de Ariel para los catorce toolkits
37
+ * ({@link TIPOS_CON_TOOLKIT}), no sólo los de este registro.
38
+ *
39
+ * {@link motivoParaNoCorrer} junta las dos y la llama node-sdk al registrar
40
+ * cada handler, así que cubre todo camino que acabe en `handler.execute`.
41
+ */
42
+ /** Lo mínimo de una herramienta de toolkit que hace falta para decidir. */
43
+ export interface OperacionDeToolkit {
44
+ operation: string;
45
+ toolName: string;
46
+ label: string;
47
+ destructive?: boolean;
48
+ }
49
+ export declare const TOOLKITS_POR_TIPO: Readonly<Record<string, readonly OperacionDeToolkit[]>>;
50
+ /**
51
+ * La operación de un toolkit, buscada por su nombre interno (`updateRow`) O
52
+ * por el nombre de su herramienta (`update_row`).
53
+ *
54
+ * Las dos formas porque las dos llegan: el AI Node guarda `operationOverride`
55
+ * con el nombre interno, y el servidor MCP recibe el nombre de la herramienta.
56
+ */
57
+ export declare function operacionDeToolkit(nodeType: string | undefined | null, operacionONombre: string | undefined | null): OperacionDeToolkit | null;
58
+ /**
59
+ * ¿Es destructiva esta operación de toolkit?
60
+ *
61
+ * `null` cuando no se sabe —el tipo no es un toolkit de este paquete, o la
62
+ * operación no existe en él—. Quien llama decide qué hace con el «no sé»; el
63
+ * gate del AI Node lo combina con los patrones de nombre, que siguen ahí para
64
+ * las herramientas que no son de toolkit (MCP, HTTP…).
65
+ */
66
+ export declare function esOperacionDestructiva(nodeType: string | undefined | null, operacionONombre: string | undefined | null): boolean | null;
67
+ /**
68
+ * Los tipos con modo AI toolkit de VARIAS herramientas: los de este registro
69
+ * más WhatsApp y Social, cuyas herramientas aún no viven aquí. Es la lista de
70
+ * quién se niega a correr sin herramienta pedida.
71
+ *
72
+ * RSS, Mailchimp o Shopify también tienen `aiEnabled`, pero exponen el nodo
73
+ * como UNA herramienta sin `operation`: no están aquí a propósito.
74
+ */
75
+ export declare const TIPOS_CON_TOOLKIT: readonly string[];
76
+ export declare function tieneModoToolkit(nodeType: string | undefined | null): boolean;
77
+ /**
78
+ * ¿Admite «Block these tool calls»? Los de este registro: para bloquear hay
79
+ * que saber traducir la operación que guarda el AI Node al nombre de la
80
+ * herramienta que guarda el nodo, y eso sólo se sabe aquí.
81
+ */
82
+ export declare function admiteBloqueo(nodeType: string | undefined | null): boolean;
83
+ /** Los nombres de herramienta de un toolkit: lo único que `blockedTools` acepta. */
84
+ export declare function nombresDeHerramientas(nodeType: string | undefined | null): string[];
85
+ /** Los nombres bloqueados de un nodo, tolerando documentos sin el campo. */
86
+ export declare function bloqueadasDe(entidad: unknown): string[];
87
+ /**
88
+ * ¿Está bloqueada en este nodo? Acepta la operación (`deleteOne`, lo que guarda
89
+ * el AI Node en `operationOverride`) o el nombre (`delete_document`, lo que
90
+ * recibe el servidor MCP).
91
+ */
92
+ export declare function herramientaBloqueadaEn(entidad: unknown, nodeType: string | undefined | null, operacionONombre: string | undefined | null): boolean;
93
+ /** Lo que se contesta a una llamada sin herramienta. Lo lee una persona en el
94
+ * historial —una arista vieja— o el modelo, así que dice qué hacer. */
95
+ export declare const MENSAJE_SIN_HERRAMIENTA = "This node is an AI toolkit: it only runs the tool an AI Node or an MCP client names, and this call named none. If a connection in the flow leads here, remove it \u2014 in toolkit mode the node is not part of the pipeline.";
96
+ export declare function mensajeDeHerramientaBloqueada(toolName: string): string;
97
+ /**
98
+ * Por qué una llamada NO debe correr en este nodo, o `null` si puede.
99
+ *
100
+ * Sólo mira nodos en modo toolkit (`aiEnabled` y un tipo de
101
+ * {@link TIPOS_CON_TOOLKIT}); cualquier otro nodo corre como siempre. En modo
102
+ * toolkit:
103
+ * - sin `operation` → {@link MENSAJE_SIN_HERRAMIENTA};
104
+ * - con una herramienta bloqueada en el nodo → su mensaje.
105
+ *
106
+ * Una operación que el registro no conoce NO se rechaza aquí: cada servicio
107
+ * sabe qué operaciones acepta (Mongo y Postgres las validan con más detalle),
108
+ * y rechazar lo desconocido aquí rompería operaciones legítimas que no son
109
+ * herramientas.
110
+ */
111
+ export declare function motivoParaNoCorrer(nodeType: string | undefined | null, entidad: unknown, payload: unknown): string | null;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Los toolkits por tipo de nodo: qué herramientas expone cada nodo en modo
3
+ * AI toolkit, en un solo sitio.
4
+ *
5
+ * ## Para qué (2026-09-24)
6
+ *
7
+ * Para que el gate «Block destructive tool calls» del AI Node pueda preguntar
8
+ * por la OPERACIÓN y no sólo por el nombre. Hasta hoy sólo miraba el nombre
9
+ * contra unos patrones (`delete_*`, `drop_*`…), así que `update_row` de Sheets
10
+ * —que pisa una fila— pasaba con el gate encendido. Con esto, la herramienta
11
+ * que el AI Node guardó al conectar (`nodeRefType` + `operationOverride`) se
12
+ * resuelve aquí a su operación, y su `destructive` —que sale del verbo, ver
13
+ * `clase-de-herramienta.ts`— decide.
14
+ *
15
+ * ## Lo que falta aquí, a propósito
16
+ *
17
+ * WhatsApp y Social siguen con sus specs fuera de este paquete (WhatsApp
18
+ * escrito a mano en la api y el dashboard; Social armado por entidad desde el
19
+ * registro de proveedores). Quien consulte este registro tiene que componerlos
20
+ * por su lado — {@link esOperacionDestructiva} devuelve `null` para ellos, que
21
+ * significa «no lo sé», no «no es destructiva». Contacts se mudó el
22
+ * 2026-09-24.
23
+ *
24
+ * ## El bloqueo por nodo y la puerta sin herramienta (2026-09-24)
25
+ *
26
+ * Abajo viven también las dos reglas que se aplican a TODA llamada a un nodo en
27
+ * modo toolkit, en un solo sitio para que la api, hw-nodes, node-sdk y el
28
+ * dashboard pregunten lo mismo:
29
+ *
30
+ * - «Block these tool calls»: el dueño bloquea herramientas en el nodo
31
+ * (`blockedTools`, por nombre). Vale para todo tipo de este registro
32
+ * ({@link admiteBloqueo}).
33
+ * - Sin herramienta no se corre: en modo toolkit un nodo sólo ejecuta la
34
+ * herramienta que le nombran en `operation`. Un evento sin ella —una arista
35
+ * vieja del lienzo— ya no corre la operación de «Single operation», que en
36
+ * ese modo no se ve. Decisión de Ariel para los catorce toolkits
37
+ * ({@link TIPOS_CON_TOOLKIT}), no sólo los de este registro.
38
+ *
39
+ * {@link motivoParaNoCorrer} junta las dos y la llama node-sdk al registrar
40
+ * cada handler, así que cubre todo camino que acabe en `handler.execute`.
41
+ */
42
+ import { GMAIL_ALL_TOOLKIT_SPECS, NATIVE_EMAIL_TOOLKIT_SPECS } from './gmail-operations.js';
43
+ import { GOOGLE_CALENDAR_TOOLKIT_SPECS } from './calendar-toolkit.js';
44
+ import { DRIVE_TOOLKIT_SPECS } from './drive-toolkit.js';
45
+ import { TELEGRAM_TOOLKIT_SPECS } from './telegram-toolkit.js';
46
+ import { DISCORD_TOOLKIT_SPECS } from './discord-toolkit.js';
47
+ import { SLACK_TOOLKIT_SPECS } from './slack-toolkit.js';
48
+ import { SHEETS_TOOLKIT_SPECS } from './sheets-toolkit.js';
49
+ import { DOCS_TOOLKIT_SPECS } from './docs-toolkit.js';
50
+ import { MONGO_TOOLKIT_SPECS } from './mongo-toolkit.js';
51
+ import { POSTGRES_TOOLKIT_SPECS } from './postgres-toolkit.js';
52
+ import { GOOGLE_CONTACTS_TOOLKIT_SPECS } from './google-contacts-toolkit.js';
53
+ export const TOOLKITS_POR_TIPO = Object.freeze({
54
+ gmailAction: GMAIL_ALL_TOOLKIT_SPECS,
55
+ emailAction: NATIVE_EMAIL_TOOLKIT_SPECS,
56
+ calendarAction: GOOGLE_CALENDAR_TOOLKIT_SPECS,
57
+ driveAction: DRIVE_TOOLKIT_SPECS,
58
+ telegramAction: TELEGRAM_TOOLKIT_SPECS,
59
+ discordAction: DISCORD_TOOLKIT_SPECS,
60
+ slackAction: SLACK_TOOLKIT_SPECS,
61
+ sheetsAction: SHEETS_TOOLKIT_SPECS,
62
+ docsAction: DOCS_TOOLKIT_SPECS,
63
+ mongoAction: MONGO_TOOLKIT_SPECS,
64
+ postgresAction: POSTGRES_TOOLKIT_SPECS,
65
+ googleContactsAction: GOOGLE_CONTACTS_TOOLKIT_SPECS,
66
+ });
67
+ /**
68
+ * La operación de un toolkit, buscada por su nombre interno (`updateRow`) O
69
+ * por el nombre de su herramienta (`update_row`).
70
+ *
71
+ * Las dos formas porque las dos llegan: el AI Node guarda `operationOverride`
72
+ * con el nombre interno, y el servidor MCP recibe el nombre de la herramienta.
73
+ */
74
+ export function operacionDeToolkit(nodeType, operacionONombre) {
75
+ if (!nodeType || !operacionONombre)
76
+ return null;
77
+ const toolkit = TOOLKITS_POR_TIPO[nodeType];
78
+ if (!toolkit)
79
+ return null;
80
+ return (toolkit.find((s) => s.operation === operacionONombre) ??
81
+ toolkit.find((s) => s.toolName === operacionONombre) ??
82
+ null);
83
+ }
84
+ /**
85
+ * ¿Es destructiva esta operación de toolkit?
86
+ *
87
+ * `null` cuando no se sabe —el tipo no es un toolkit de este paquete, o la
88
+ * operación no existe en él—. Quien llama decide qué hace con el «no sé»; el
89
+ * gate del AI Node lo combina con los patrones de nombre, que siguen ahí para
90
+ * las herramientas que no son de toolkit (MCP, HTTP…).
91
+ */
92
+ export function esOperacionDestructiva(nodeType, operacionONombre) {
93
+ const op = operacionDeToolkit(nodeType, operacionONombre);
94
+ return op ? op.destructive === true : null;
95
+ }
96
+ /* ── Quién tiene toolkit, el bloqueo por nodo y la puerta ───────────────── */
97
+ /**
98
+ * Los tipos con modo AI toolkit de VARIAS herramientas: los de este registro
99
+ * más WhatsApp y Social, cuyas herramientas aún no viven aquí. Es la lista de
100
+ * quién se niega a correr sin herramienta pedida.
101
+ *
102
+ * RSS, Mailchimp o Shopify también tienen `aiEnabled`, pero exponen el nodo
103
+ * como UNA herramienta sin `operation`: no están aquí a propósito.
104
+ */
105
+ export const TIPOS_CON_TOOLKIT = Object.freeze([
106
+ ...Object.keys(TOOLKITS_POR_TIPO),
107
+ 'whatsappAction',
108
+ 'socialMediaAction',
109
+ ]);
110
+ export function tieneModoToolkit(nodeType) {
111
+ return !!nodeType && TIPOS_CON_TOOLKIT.includes(nodeType);
112
+ }
113
+ /**
114
+ * ¿Admite «Block these tool calls»? Los de este registro: para bloquear hay
115
+ * que saber traducir la operación que guarda el AI Node al nombre de la
116
+ * herramienta que guarda el nodo, y eso sólo se sabe aquí.
117
+ */
118
+ export function admiteBloqueo(nodeType) {
119
+ return !!nodeType && Object.prototype.hasOwnProperty.call(TOOLKITS_POR_TIPO, nodeType);
120
+ }
121
+ /** Los nombres de herramienta de un toolkit: lo único que `blockedTools` acepta. */
122
+ export function nombresDeHerramientas(nodeType) {
123
+ if (!admiteBloqueo(nodeType))
124
+ return [];
125
+ return TOOLKITS_POR_TIPO[nodeType].map((s) => s.toolName);
126
+ }
127
+ /** Los nombres bloqueados de un nodo, tolerando documentos sin el campo. */
128
+ export function bloqueadasDe(entidad) {
129
+ const lista = entidad?.blockedTools;
130
+ return Array.isArray(lista) ? lista.filter((x) => typeof x === 'string') : [];
131
+ }
132
+ /**
133
+ * ¿Está bloqueada en este nodo? Acepta la operación (`deleteOne`, lo que guarda
134
+ * el AI Node en `operationOverride`) o el nombre (`delete_document`, lo que
135
+ * recibe el servidor MCP).
136
+ */
137
+ export function herramientaBloqueadaEn(entidad, nodeType, operacionONombre) {
138
+ if (!operacionONombre)
139
+ return false;
140
+ const bloqueadas = bloqueadasDe(entidad);
141
+ if (bloqueadas.length === 0)
142
+ return false;
143
+ const op = operacionDeToolkit(nodeType, operacionONombre);
144
+ return bloqueadas.includes(op?.toolName ?? operacionONombre);
145
+ }
146
+ /** Lo que se contesta a una llamada sin herramienta. Lo lee una persona en el
147
+ * historial —una arista vieja— o el modelo, así que dice qué hacer. */
148
+ export const MENSAJE_SIN_HERRAMIENTA = 'This node is an AI toolkit: it only runs the tool an AI Node or an MCP client names, and this call named none. If a connection in the flow leads here, remove it — in toolkit mode the node is not part of the pipeline.';
149
+ export function mensajeDeHerramientaBloqueada(toolName) {
150
+ return `The tool "${toolName}" is blocked on this node by its owner. Do not retry it; tell the user it is not allowed here.`;
151
+ }
152
+ /**
153
+ * Por qué una llamada NO debe correr en este nodo, o `null` si puede.
154
+ *
155
+ * Sólo mira nodos en modo toolkit (`aiEnabled` y un tipo de
156
+ * {@link TIPOS_CON_TOOLKIT}); cualquier otro nodo corre como siempre. En modo
157
+ * toolkit:
158
+ * - sin `operation` → {@link MENSAJE_SIN_HERRAMIENTA};
159
+ * - con una herramienta bloqueada en el nodo → su mensaje.
160
+ *
161
+ * Una operación que el registro no conoce NO se rechaza aquí: cada servicio
162
+ * sabe qué operaciones acepta (Mongo y Postgres las validan con más detalle),
163
+ * y rechazar lo desconocido aquí rompería operaciones legítimas que no son
164
+ * herramientas.
165
+ */
166
+ export function motivoParaNoCorrer(nodeType, entidad, payload) {
167
+ const e = entidad;
168
+ if (!e || e.aiEnabled !== true || !tieneModoToolkit(nodeType))
169
+ return null;
170
+ const pedida = payload?.operation;
171
+ if (typeof pedida !== 'string' || !pedida.trim())
172
+ return MENSAJE_SIN_HERRAMIENTA;
173
+ if (herramientaBloqueadaEn(entidad, nodeType, pedida)) {
174
+ const op = operacionDeToolkit(nodeType, pedida);
175
+ return mensajeDeHerramientaBloqueada(op?.toolName ?? pedida);
176
+ }
177
+ return null;
178
+ }
@@ -151,6 +151,8 @@ export interface GmailToolkitSpec {
151
151
  toolName: string;
152
152
  description: string;
153
153
  parameters: GmailToolkitParameter[];
154
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
155
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
154
156
  destructive?: boolean;
155
157
  }
156
158
  export declare const GMAIL_TOOLKIT_SPECS: GmailToolkitSpec[];