@hostwebhook/node-types 1.85.0 → 1.87.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 (65) 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/index.d.ts +12 -0
  24. package/dist/esm/index.js +7 -0
  25. package/dist/esm/mongo-toolkit.d.ts +47 -0
  26. package/dist/esm/mongo-toolkit.js +98 -0
  27. package/dist/esm/postgres-toolkit.d.ts +52 -0
  28. package/dist/esm/postgres-toolkit.js +85 -0
  29. package/dist/esm/registry.js +10 -0
  30. package/dist/esm/sentiment.d.ts +39 -0
  31. package/dist/esm/sentiment.js +53 -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 +47 -0
  39. package/dist/esm/toolkits.js +74 -0
  40. package/dist/esm/types.d.ts +111 -76
  41. package/dist/esm/types.js +26 -0
  42. package/dist/esm/ui.js +10 -1
  43. package/dist/gmail-operations.d.ts +2 -0
  44. package/dist/gmail-operations.js +11 -10
  45. package/dist/index.d.ts +12 -0
  46. package/dist/index.js +29 -4
  47. package/dist/mongo-toolkit.d.ts +47 -0
  48. package/dist/mongo-toolkit.js +101 -0
  49. package/dist/postgres-toolkit.d.ts +52 -0
  50. package/dist/postgres-toolkit.js +88 -0
  51. package/dist/registry.js +10 -0
  52. package/dist/sentiment.d.ts +39 -0
  53. package/dist/sentiment.js +57 -0
  54. package/dist/sheets-toolkit.d.ts +2 -1
  55. package/dist/sheets-toolkit.js +3 -6
  56. package/dist/slack-toolkit.d.ts +2 -3
  57. package/dist/slack-toolkit.js +3 -4
  58. package/dist/telegram-toolkit.d.ts +3 -0
  59. package/dist/telegram-toolkit.js +3 -2
  60. package/dist/toolkits.d.ts +47 -0
  61. package/dist/toolkits.js +79 -0
  62. package/dist/types.d.ts +111 -76
  63. package/dist/types.js +28 -0
  64. package/dist/ui.js +10 -1
  65. package/package.json +1 -1
@@ -44,6 +44,9 @@ export interface GoogleCalendarToolkitSpec {
44
44
  /** Descripción y reglas de uso que ve el LLM. */
45
45
  description: string;
46
46
  parameters: GoogleCalendarToolkitParameter[];
47
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
48
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
49
+ destructive?: boolean;
47
50
  }
48
51
  export declare const GOOGLE_CALENDAR_TOOLKIT_SPECS: GoogleCalendarToolkitSpec[];
49
52
  /** Índice por nombre de herramienta, para enrutar un `tools/call` sin recorrer. */
@@ -31,8 +31,9 @@
31
31
  */
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = void 0;
34
+ const clase_de_herramienta_js_1 = require("./clase-de-herramienta.js");
34
35
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
35
- exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = [
36
+ exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = (0, clase_de_herramienta_js_1.marcarDestructivas)([
36
37
  {
37
38
  operation: "createEvent",
38
39
  label: "Create event",
@@ -150,6 +151,6 @@ exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = [
150
151
  p("calendarId", "Optional calendar id. Defaults to the configured calendar (usually 'primary'). Use list_my_calendars to discover ids.", false),
151
152
  ],
152
153
  },
153
- ];
154
+ ]);
154
155
  /** Índice por nombre de herramienta, para enrutar un `tools/call` sin recorrer. */
155
156
  exports.GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.GOOGLE_CALENDAR_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Qué le hace al mundo una herramienta, leído de su nombre: la ÚNICA
3
+ * definición de «destructiva» que comparten el AI Node, el servidor MCP y el
4
+ * dashboard.
5
+ *
6
+ * ## Por qué vive aquí (2026-09-24)
7
+ *
8
+ * Había tres definiciones y ninguna coincidía con otra:
9
+ *
10
+ * 1. los patrones de nombre del gate del AI Node (`delete_*`, `drop_*`…),
11
+ * que es lo ÚNICO que el gate miraba al ejecutar;
12
+ * 2. el `destructive: true` escrito a mano en cada operación de toolkit, que
13
+ * los comentarios decían que usaba el gate y que no leía nadie — y que
14
+ * además estaba puesto sin criterio: Docs marcaba `create_doc`, Slack
15
+ * `create_slack_channel`, y Gmail no marcaba ni `delete_email`;
16
+ * 3. la tabla de verbos de las anotaciones MCP de la api, la única con un
17
+ * criterio escrito.
18
+ *
19
+ * Se queda la tercera, y se sube aquí para que las otras dos la usen. El flag
20
+ * de cada operación ya no se escribe a mano: lo pone {@link marcarDestructivas}
21
+ * al cargar el módulo, así que no hay nada que se pueda separar del verbo.
22
+ *
23
+ * ## El criterio
24
+ *
25
+ * lectura no toca nada
26
+ * escritura añade, no pisa (crear, añadir, enviar…)
27
+ * destructiva pisa o borra algo que ya existía
28
+ *
29
+ * Por VERBO —el primer segmento del nombre— y no una lista de cien nombres,
30
+ * porque los nombres ya son regulares y una lista se queda vieja en cuanto
31
+ * alguien añade una operación. Las excepciones son los nombres cuyo verbo
32
+ * miente, cada una con su porqué.
33
+ */
34
+ export type ClaseDeHerramienta = 'lectura' | 'escritura' | 'destructiva';
35
+ /**
36
+ * La clase de una herramienta, o `null` si ningún verbo la reconoce.
37
+ *
38
+ * El `null` es para las pruebas: una herramienta nueva con un verbo que nadie
39
+ * conoce tiene que ponerlas en rojo, no colarse clasificada por descuido.
40
+ */
41
+ export declare function claseDeHerramienta(toolName: string): ClaseDeHerramienta | null;
42
+ /**
43
+ * ¿Es destructiva? Lo que no se reconoce SE TRATA COMO DESTRUCTIVO: ante una
44
+ * herramienta que no sabemos qué hace, la respuesta segura para un gate es
45
+ * pararla, no dejarla pasar.
46
+ */
47
+ export declare function esHerramientaDestructiva(toolName: string): boolean;
48
+ /**
49
+ * Pone el `destructive` de cada operación de un toolkit a partir de su nombre.
50
+ *
51
+ * Se llama UNA vez, al cargar el módulo de cada toolkit, sobre su propio
52
+ * array. Muta en sitio a propósito: los `*_BY_TOOL_NAME` y cualquier otro
53
+ * índice apuntan a los MISMOS objetos, así que ven el flag sin tener que
54
+ * reconstruirse. `true` cuando es destructiva; cuando no, la clave desaparece
55
+ * — un `destructive: false` explícito se leería como una decisión que nadie
56
+ * tomó.
57
+ */
58
+ export declare function marcarDestructivas<T extends {
59
+ toolName: string;
60
+ destructive?: boolean;
61
+ }>(specs: T[]): T[];
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Qué le hace al mundo una herramienta, leído de su nombre: la ÚNICA
4
+ * definición de «destructiva» que comparten el AI Node, el servidor MCP y el
5
+ * dashboard.
6
+ *
7
+ * ## Por qué vive aquí (2026-09-24)
8
+ *
9
+ * Había tres definiciones y ninguna coincidía con otra:
10
+ *
11
+ * 1. los patrones de nombre del gate del AI Node (`delete_*`, `drop_*`…),
12
+ * que es lo ÚNICO que el gate miraba al ejecutar;
13
+ * 2. el `destructive: true` escrito a mano en cada operación de toolkit, que
14
+ * los comentarios decían que usaba el gate y que no leía nadie — y que
15
+ * además estaba puesto sin criterio: Docs marcaba `create_doc`, Slack
16
+ * `create_slack_channel`, y Gmail no marcaba ni `delete_email`;
17
+ * 3. la tabla de verbos de las anotaciones MCP de la api, la única con un
18
+ * criterio escrito.
19
+ *
20
+ * Se queda la tercera, y se sube aquí para que las otras dos la usen. El flag
21
+ * de cada operación ya no se escribe a mano: lo pone {@link marcarDestructivas}
22
+ * al cargar el módulo, así que no hay nada que se pueda separar del verbo.
23
+ *
24
+ * ## El criterio
25
+ *
26
+ * lectura no toca nada
27
+ * escritura añade, no pisa (crear, añadir, enviar…)
28
+ * destructiva pisa o borra algo que ya existía
29
+ *
30
+ * Por VERBO —el primer segmento del nombre— y no una lista de cien nombres,
31
+ * porque los nombres ya son regulares y una lista se queda vieja en cuanto
32
+ * alguien añade una operación. Las excepciones son los nombres cuyo verbo
33
+ * miente, cada una con su porqué.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.claseDeHerramienta = claseDeHerramienta;
37
+ exports.esHerramientaDestructiva = esHerramientaDestructiva;
38
+ exports.marcarDestructivas = marcarDestructivas;
39
+ const POR_VERBO = Object.freeze({
40
+ // Lectura
41
+ get: 'lectura',
42
+ list: 'lectura',
43
+ search: 'lectura',
44
+ read: 'lectura',
45
+ download: 'lectura',
46
+ export: 'lectura',
47
+ find: 'lectura',
48
+ /* Los dos de las bases de datos. `query_rows` de Postgres corre dentro de
49
+ una transacción READ ONLY —no escribe aunque el SQL lo intente— y
50
+ `aggregate_documents` de Mongo rechaza `$out` y `$merge`, que son las dos
51
+ etapas que escriben. */
52
+ query: 'lectura',
53
+ aggregate: 'lectura',
54
+ // Escritura que sólo añade
55
+ send: 'escritura',
56
+ create: 'escritura',
57
+ add: 'escritura',
58
+ append: 'escritura',
59
+ insert: 'escritura',
60
+ upload: 'escritura',
61
+ draft: 'escritura',
62
+ schedule: 'escritura',
63
+ reply: 'escritura',
64
+ forward: 'escritura',
65
+ invite: 'escritura',
66
+ share: 'escritura',
67
+ copy: 'escritura',
68
+ pin: 'escritura',
69
+ untrash: 'escritura',
70
+ mark: 'escritura',
71
+ answer: 'escritura',
72
+ /* El par de Calendar: `propose_` crea un evento tentativo sin invitar a
73
+ nadie, `confirm_` lo promociona y manda las invitaciones. Ninguno pisa
74
+ nada; mandar invitaciones pesa lo mismo que un `send_email`. */
75
+ propose: 'escritura',
76
+ confirm: 'escritura',
77
+ // Escritura que pisa o borra
78
+ update: 'destructiva',
79
+ edit: 'destructiva',
80
+ replace: 'destructiva',
81
+ delete: 'destructiva',
82
+ remove: 'destructiva',
83
+ trash: 'destructiva',
84
+ move: 'destructiva',
85
+ /* `execute_sql` de Postgres: SQL cualquiera, DDL incluido. Siempre
86
+ destructiva, por decisión de Ariel (2026-09-24). */
87
+ execute: 'destructiva',
88
+ });
89
+ /**
90
+ * Los nombres cuyo verbo miente.
91
+ *
92
+ * Una entrada aquí es una decisión, y cada una lleva la suya: sin el porqué,
93
+ * la siguiente persona la quitaría por «incoherente».
94
+ */
95
+ const EXCEPCIONES = Object.freeze({
96
+ /* Empieza por `append` y termina pisando la fila que encuentre. */
97
+ append_or_update_row: 'destructiva',
98
+ /* Los `batch_*` no dicen en el primer segmento qué hacen: hay que leer el
99
+ segundo. */
100
+ batch_create_contacts: 'escritura',
101
+ batch_update_contacts: 'destructiva',
102
+ batch_delete_contacts: 'destructiva',
103
+ /* Compartir no pisa nada, pero expone: un fichero compartido con quien no
104
+ debía ya ha salido, y eso no se deshace quitando el permiso después. Para
105
+ un agente que decide solo, pesa como un borrado. */
106
+ share_drive_file: 'destructiva',
107
+ /* Añadir un rol no borra nada, pero puede dar permisos de administrador. Es
108
+ el mismo peso que quitarlo, que ya era destructiva por su verbo. */
109
+ add_discord_role: 'destructiva',
110
+ });
111
+ /**
112
+ * La clase de una herramienta, o `null` si ningún verbo la reconoce.
113
+ *
114
+ * El `null` es para las pruebas: una herramienta nueva con un verbo que nadie
115
+ * conoce tiene que ponerlas en rojo, no colarse clasificada por descuido.
116
+ */
117
+ function claseDeHerramienta(toolName) {
118
+ const nombre = (toolName ?? '').trim().toLowerCase();
119
+ if (!nombre)
120
+ return null;
121
+ const excepcion = EXCEPCIONES[nombre];
122
+ if (excepcion)
123
+ return excepcion;
124
+ return POR_VERBO[nombre.split('_')[0]] ?? null;
125
+ }
126
+ /**
127
+ * ¿Es destructiva? Lo que no se reconoce SE TRATA COMO DESTRUCTIVO: ante una
128
+ * herramienta que no sabemos qué hace, la respuesta segura para un gate es
129
+ * pararla, no dejarla pasar.
130
+ */
131
+ function esHerramientaDestructiva(toolName) {
132
+ const clase = claseDeHerramienta(toolName);
133
+ return clase === null || clase === 'destructiva';
134
+ }
135
+ /**
136
+ * Pone el `destructive` de cada operación de un toolkit a partir de su nombre.
137
+ *
138
+ * Se llama UNA vez, al cargar el módulo de cada toolkit, sobre su propio
139
+ * array. Muta en sitio a propósito: los `*_BY_TOOL_NAME` y cualquier otro
140
+ * índice apuntan a los MISMOS objetos, así que ven el flag sin tener que
141
+ * reconstruirse. `true` cuando es destructiva; cuando no, la clave desaparece
142
+ * — un `destructive: false` explícito se leería como una decisión que nadie
143
+ * tomó.
144
+ */
145
+ function marcarDestructivas(specs) {
146
+ for (const spec of specs) {
147
+ if (esHerramientaDestructiva(spec.toolName))
148
+ spec.destructive = true;
149
+ else
150
+ delete spec.destructive;
151
+ }
152
+ return specs;
153
+ }
@@ -38,9 +38,8 @@ export interface DiscordToolkitSpec {
38
38
  /** Descripción (más reglas de uso) que ve el LLM. */
39
39
  description: string;
40
40
  parameters: DiscordToolkitParameter[];
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 DISCORD_TOOLKIT_SPECS: DiscordToolkitSpec[];
@@ -27,8 +27,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
27
27
  exports.DISCORD_TOOLKIT_BY_TOOL_NAME = exports.DISCORD_TOOLKIT_SPECS = void 0;
28
28
  exports.herramientasDeDiscordPara = herramientasDeDiscordPara;
29
29
  const discord_operations_js_1 = require("./discord-operations.js");
30
+ const clase_de_herramienta_js_1 = require("./clase-de-herramienta.js");
30
31
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
31
- exports.DISCORD_TOOLKIT_SPECS = [
32
+ exports.DISCORD_TOOLKIT_SPECS = (0, clase_de_herramienta_js_1.marcarDestructivas)([
32
33
  // ── Messages ───────────────────────────────────────────────────
33
34
  {
34
35
  operation: 'sendMessage',
@@ -67,7 +68,6 @@ exports.DISCORD_TOOLKIT_SPECS = [
67
68
  p('channelId', 'Channel snowflake where the message lives.'),
68
69
  p('messageId', 'Snowflake of the message to delete.'),
69
70
  ],
70
- destructive: true,
71
71
  },
72
72
  {
73
73
  operation: 'getMessage',
@@ -153,7 +153,6 @@ exports.DISCORD_TOOLKIT_SPECS = [
153
153
  toolName: 'delete_discord_channel',
154
154
  description: 'Permanently delete a Discord channel and ALL its messages. Irreversible. ALWAYS confirm with the user before calling — Discord does not provide a recovery window.',
155
155
  parameters: [p('channelId', 'Channel snowflake to delete.')],
156
- destructive: true,
157
156
  },
158
157
  {
159
158
  operation: 'getChannel',
@@ -209,7 +208,6 @@ exports.DISCORD_TOOLKIT_SPECS = [
209
208
  p('userId', 'Member snowflake.'),
210
209
  p('roleId', 'Role snowflake to grant.'),
211
210
  ],
212
- destructive: true,
213
211
  },
214
212
  {
215
213
  operation: 'removeRole',
@@ -221,7 +219,6 @@ exports.DISCORD_TOOLKIT_SPECS = [
221
219
  p('userId', 'Member snowflake.'),
222
220
  p('roleId', 'Role snowflake to remove.'),
223
221
  ],
224
- destructive: true,
225
222
  },
226
223
  {
227
224
  operation: 'getMember',
@@ -233,7 +230,7 @@ exports.DISCORD_TOOLKIT_SPECS = [
233
230
  p('userId', 'Member snowflake.'),
234
231
  ],
235
232
  },
236
- ];
233
+ ]);
237
234
  exports.DISCORD_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.DISCORD_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
238
235
  /**
239
236
  * Las herramientas que ofrecerle al modelo con ESTA credencial.
@@ -27,7 +27,8 @@ export interface DocsToolkitSpec {
27
27
  toolName: string;
28
28
  description: string;
29
29
  parameters: DocsToolkitParameter[];
30
- /** Escribe en el documento del usuario. Lo usa la confirmación previa del AI Node. */
30
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
31
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
31
32
  destructive?: boolean;
32
33
  }
33
34
  export declare const DOCS_TOOLKIT_SPECS: DocsToolkitSpec[];
@@ -16,6 +16,7 @@
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = void 0;
19
+ const clase_de_herramienta_js_1 = require("./clase-de-herramienta.js");
19
20
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
20
21
  /*
21
22
  * El documento del nodo va como **defecto** y el modelo puede cambiarlo — la
@@ -34,7 +35,7 @@ const p = (name, description, required = true, type = 'string') => ({ name, type
34
35
  * documento nuevo nacería con el id del viejo por título.
35
36
  */
36
37
  const documento = () => p('documentId', 'Id of the Google Docs document — the long chunk of its URL, between /d/ and /edit.');
37
- exports.DOCS_TOOLKIT_SPECS = [
38
+ exports.DOCS_TOOLKIT_SPECS = (0, clase_de_herramienta_js_1.marcarDestructivas)([
38
39
  {
39
40
  operation: 'readDoc',
40
41
  label: 'Read document',
@@ -51,7 +52,6 @@ exports.DOCS_TOOLKIT_SPECS = [
51
52
  p('title', 'Name of the new document.'),
52
53
  p('content', 'Initial body, as HTML. Basic tags work (<p>, <b>, <i>, <ul>, <li>, <h1>…). Plain text is fine too. Leave it out for an empty document.', false),
53
54
  ],
54
- destructive: true,
55
55
  },
56
56
  {
57
57
  operation: 'appendText',
@@ -62,7 +62,6 @@ exports.DOCS_TOOLKIT_SPECS = [
62
62
  p('content', 'What to add, as HTML. Basic tags work (<p>, <b>, <i>, <ul>, <li>, <h1>…). Plain text is fine too.'),
63
63
  documento(),
64
64
  ],
65
- destructive: true,
66
65
  },
67
66
  {
68
67
  operation: 'replaceText',
@@ -74,7 +73,6 @@ exports.DOCS_TOOLKIT_SPECS = [
74
73
  p('replaceWith', 'Text to put in its place. An empty string deletes the matches.'),
75
74
  documento(),
76
75
  ],
77
- destructive: true,
78
76
  },
79
77
  {
80
78
  operation: 'insertTable',
@@ -86,9 +84,8 @@ exports.DOCS_TOOLKIT_SPECS = [
86
84
  p('cols', 'Number of columns.', true, 'number'),
87
85
  documento(),
88
86
  ],
89
- destructive: true,
90
87
  },
91
- ];
88
+ ]);
92
89
  /** Las herramientas por nombre, para despachar una llamada del modelo. */
93
90
  exports.DOCS_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.DOCS_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
94
91
  /**
@@ -42,10 +42,15 @@ export interface DriveToolkitSpec {
42
42
  /** Encabezado de grupo de la página de detalle. El orden es el que agrupa. */
43
43
  group?: string;
44
44
  /**
45
- * Marca que la herramienta cambia algo difícil de deshacer, para que la página
46
- * lo enseñe y el AI Node pida confirmación. Son **dos**, las mismas que ya
47
- * estaban marcadas: `delete_drive_file` y `share_drive_file`. `move` no lo
48
- * lleva, y es defendible — deshacerlo es otro move.
45
+ * Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del nombre
46
+ * (ver `clase-de-herramienta.ts`), no se escribe a mano.
47
+ *
48
+ * ⚠️ Hasta el 2026-09-24 aquí se decía que `move` no lo llevaba —«deshacerlo
49
+ * es otro move»—, mientras la tabla de anotaciones MCP de la api decía lo
50
+ * contrario: sacar el fichero de su carpeta es quitarlo de donde estaba. Eran
51
+ * dos criterios para lo mismo; se queda el de la tabla, que es el único que
52
+ * estaba escrito como regla. `share_drive_file` sigue marcada, ahora como
53
+ * excepción explícita: compartir expone, y eso no se deshace.
49
54
  */
50
55
  destructive?: boolean;
51
56
  }
@@ -22,11 +22,12 @@
22
22
  */
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.DRIVE_TOOLKIT_BY_TOOL_NAME = exports.DRIVE_TOOLKIT_SPECS = void 0;
25
+ const clase_de_herramienta_js_1 = require("./clase-de-herramienta.js");
25
26
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
26
27
  const ENTRAN_Y_SALEN = 'Files in / out';
27
28
  const DESCUBRIR = 'Discovery';
28
29
  const MUTACIONES = 'Mutations (LLM confirms first)';
29
- exports.DRIVE_TOOLKIT_SPECS = [
30
+ exports.DRIVE_TOOLKIT_SPECS = (0, clase_de_herramienta_js_1.marcarDestructivas)([
30
31
  /* ── Files in / out ─────────────────────────────────────────────────── */
31
32
  {
32
33
  operation: 'upload',
@@ -157,7 +158,6 @@ exports.DRIVE_TOOLKIT_SPECS = [
157
158
  p('fileId', 'The Drive file ID to delete.'),
158
159
  p('permanent', 'Optional. true = bypass trash and erase forever (irreversible). Default false.', false, 'boolean'),
159
160
  ],
160
- destructive: true,
161
161
  },
162
162
  {
163
163
  operation: 'share',
@@ -173,8 +173,7 @@ exports.DRIVE_TOOLKIT_SPECS = [
173
173
  p('domain', 'Required when type=domain.', false),
174
174
  p('sendNotificationEmail', 'Optional. true = email the recipient about the share.', false, 'boolean'),
175
175
  ],
176
- destructive: true,
177
176
  },
178
- ];
177
+ ]);
179
178
  /** Índice por nombre de herramienta, para enrutar un `tools/call` sin recorrer. */
180
179
  exports.DRIVE_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.DRIVE_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -44,6 +44,9 @@ export interface GoogleCalendarToolkitSpec {
44
44
  /** Descripción y reglas de uso que ve el LLM. */
45
45
  description: string;
46
46
  parameters: GoogleCalendarToolkitParameter[];
47
+ /** Pisa o borra. Lo pone `marcarDestructivas` leyendo el verbo del
48
+ * nombre (ver `clase-de-herramienta.ts`), no se escribe a mano. */
49
+ destructive?: boolean;
47
50
  }
48
51
  export declare const GOOGLE_CALENDAR_TOOLKIT_SPECS: GoogleCalendarToolkitSpec[];
49
52
  /** Índice por nombre de herramienta, para enrutar un `tools/call` sin recorrer. */
@@ -28,8 +28,9 @@
28
28
  * El array se generó desde la copia de la api para que las descripciones largas
29
29
  * salieran idénticas, no tecleadas.
30
30
  */
31
+ import { marcarDestructivas } from './clase-de-herramienta.js';
31
32
  const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
32
- export const GOOGLE_CALENDAR_TOOLKIT_SPECS = [
33
+ export const GOOGLE_CALENDAR_TOOLKIT_SPECS = marcarDestructivas([
33
34
  {
34
35
  operation: "createEvent",
35
36
  label: "Create event",
@@ -147,6 +148,6 @@ export const GOOGLE_CALENDAR_TOOLKIT_SPECS = [
147
148
  p("calendarId", "Optional calendar id. Defaults to the configured calendar (usually 'primary'). Use list_my_calendars to discover ids.", false),
148
149
  ],
149
150
  },
150
- ];
151
+ ]);
151
152
  /** Índice por nombre de herramienta, para enrutar un `tools/call` sin recorrer. */
152
153
  export const GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(GOOGLE_CALENDAR_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Qué le hace al mundo una herramienta, leído de su nombre: la ÚNICA
3
+ * definición de «destructiva» que comparten el AI Node, el servidor MCP y el
4
+ * dashboard.
5
+ *
6
+ * ## Por qué vive aquí (2026-09-24)
7
+ *
8
+ * Había tres definiciones y ninguna coincidía con otra:
9
+ *
10
+ * 1. los patrones de nombre del gate del AI Node (`delete_*`, `drop_*`…),
11
+ * que es lo ÚNICO que el gate miraba al ejecutar;
12
+ * 2. el `destructive: true` escrito a mano en cada operación de toolkit, que
13
+ * los comentarios decían que usaba el gate y que no leía nadie — y que
14
+ * además estaba puesto sin criterio: Docs marcaba `create_doc`, Slack
15
+ * `create_slack_channel`, y Gmail no marcaba ni `delete_email`;
16
+ * 3. la tabla de verbos de las anotaciones MCP de la api, la única con un
17
+ * criterio escrito.
18
+ *
19
+ * Se queda la tercera, y se sube aquí para que las otras dos la usen. El flag
20
+ * de cada operación ya no se escribe a mano: lo pone {@link marcarDestructivas}
21
+ * al cargar el módulo, así que no hay nada que se pueda separar del verbo.
22
+ *
23
+ * ## El criterio
24
+ *
25
+ * lectura no toca nada
26
+ * escritura añade, no pisa (crear, añadir, enviar…)
27
+ * destructiva pisa o borra algo que ya existía
28
+ *
29
+ * Por VERBO —el primer segmento del nombre— y no una lista de cien nombres,
30
+ * porque los nombres ya son regulares y una lista se queda vieja en cuanto
31
+ * alguien añade una operación. Las excepciones son los nombres cuyo verbo
32
+ * miente, cada una con su porqué.
33
+ */
34
+ export type ClaseDeHerramienta = 'lectura' | 'escritura' | 'destructiva';
35
+ /**
36
+ * La clase de una herramienta, o `null` si ningún verbo la reconoce.
37
+ *
38
+ * El `null` es para las pruebas: una herramienta nueva con un verbo que nadie
39
+ * conoce tiene que ponerlas en rojo, no colarse clasificada por descuido.
40
+ */
41
+ export declare function claseDeHerramienta(toolName: string): ClaseDeHerramienta | null;
42
+ /**
43
+ * ¿Es destructiva? Lo que no se reconoce SE TRATA COMO DESTRUCTIVO: ante una
44
+ * herramienta que no sabemos qué hace, la respuesta segura para un gate es
45
+ * pararla, no dejarla pasar.
46
+ */
47
+ export declare function esHerramientaDestructiva(toolName: string): boolean;
48
+ /**
49
+ * Pone el `destructive` de cada operación de un toolkit a partir de su nombre.
50
+ *
51
+ * Se llama UNA vez, al cargar el módulo de cada toolkit, sobre su propio
52
+ * array. Muta en sitio a propósito: los `*_BY_TOOL_NAME` y cualquier otro
53
+ * índice apuntan a los MISMOS objetos, así que ven el flag sin tener que
54
+ * reconstruirse. `true` cuando es destructiva; cuando no, la clave desaparece
55
+ * — un `destructive: false` explícito se leería como una decisión que nadie
56
+ * tomó.
57
+ */
58
+ export declare function marcarDestructivas<T extends {
59
+ toolName: string;
60
+ destructive?: boolean;
61
+ }>(specs: T[]): T[];
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Qué le hace al mundo una herramienta, leído de su nombre: la ÚNICA
3
+ * definición de «destructiva» que comparten el AI Node, el servidor MCP y el
4
+ * dashboard.
5
+ *
6
+ * ## Por qué vive aquí (2026-09-24)
7
+ *
8
+ * Había tres definiciones y ninguna coincidía con otra:
9
+ *
10
+ * 1. los patrones de nombre del gate del AI Node (`delete_*`, `drop_*`…),
11
+ * que es lo ÚNICO que el gate miraba al ejecutar;
12
+ * 2. el `destructive: true` escrito a mano en cada operación de toolkit, que
13
+ * los comentarios decían que usaba el gate y que no leía nadie — y que
14
+ * además estaba puesto sin criterio: Docs marcaba `create_doc`, Slack
15
+ * `create_slack_channel`, y Gmail no marcaba ni `delete_email`;
16
+ * 3. la tabla de verbos de las anotaciones MCP de la api, la única con un
17
+ * criterio escrito.
18
+ *
19
+ * Se queda la tercera, y se sube aquí para que las otras dos la usen. El flag
20
+ * de cada operación ya no se escribe a mano: lo pone {@link marcarDestructivas}
21
+ * al cargar el módulo, así que no hay nada que se pueda separar del verbo.
22
+ *
23
+ * ## El criterio
24
+ *
25
+ * lectura no toca nada
26
+ * escritura añade, no pisa (crear, añadir, enviar…)
27
+ * destructiva pisa o borra algo que ya existía
28
+ *
29
+ * Por VERBO —el primer segmento del nombre— y no una lista de cien nombres,
30
+ * porque los nombres ya son regulares y una lista se queda vieja en cuanto
31
+ * alguien añade una operación. Las excepciones son los nombres cuyo verbo
32
+ * miente, cada una con su porqué.
33
+ */
34
+ const POR_VERBO = Object.freeze({
35
+ // Lectura
36
+ get: 'lectura',
37
+ list: 'lectura',
38
+ search: 'lectura',
39
+ read: 'lectura',
40
+ download: 'lectura',
41
+ export: 'lectura',
42
+ find: 'lectura',
43
+ /* Los dos de las bases de datos. `query_rows` de Postgres corre dentro de
44
+ una transacción READ ONLY —no escribe aunque el SQL lo intente— y
45
+ `aggregate_documents` de Mongo rechaza `$out` y `$merge`, que son las dos
46
+ etapas que escriben. */
47
+ query: 'lectura',
48
+ aggregate: 'lectura',
49
+ // Escritura que sólo añade
50
+ send: 'escritura',
51
+ create: 'escritura',
52
+ add: 'escritura',
53
+ append: 'escritura',
54
+ insert: 'escritura',
55
+ upload: 'escritura',
56
+ draft: 'escritura',
57
+ schedule: 'escritura',
58
+ reply: 'escritura',
59
+ forward: 'escritura',
60
+ invite: 'escritura',
61
+ share: 'escritura',
62
+ copy: 'escritura',
63
+ pin: 'escritura',
64
+ untrash: 'escritura',
65
+ mark: 'escritura',
66
+ answer: 'escritura',
67
+ /* El par de Calendar: `propose_` crea un evento tentativo sin invitar a
68
+ nadie, `confirm_` lo promociona y manda las invitaciones. Ninguno pisa
69
+ nada; mandar invitaciones pesa lo mismo que un `send_email`. */
70
+ propose: 'escritura',
71
+ confirm: 'escritura',
72
+ // Escritura que pisa o borra
73
+ update: 'destructiva',
74
+ edit: 'destructiva',
75
+ replace: 'destructiva',
76
+ delete: 'destructiva',
77
+ remove: 'destructiva',
78
+ trash: 'destructiva',
79
+ move: 'destructiva',
80
+ /* `execute_sql` de Postgres: SQL cualquiera, DDL incluido. Siempre
81
+ destructiva, por decisión de Ariel (2026-09-24). */
82
+ execute: 'destructiva',
83
+ });
84
+ /**
85
+ * Los nombres cuyo verbo miente.
86
+ *
87
+ * Una entrada aquí es una decisión, y cada una lleva la suya: sin el porqué,
88
+ * la siguiente persona la quitaría por «incoherente».
89
+ */
90
+ const EXCEPCIONES = Object.freeze({
91
+ /* Empieza por `append` y termina pisando la fila que encuentre. */
92
+ append_or_update_row: 'destructiva',
93
+ /* Los `batch_*` no dicen en el primer segmento qué hacen: hay que leer el
94
+ segundo. */
95
+ batch_create_contacts: 'escritura',
96
+ batch_update_contacts: 'destructiva',
97
+ batch_delete_contacts: 'destructiva',
98
+ /* Compartir no pisa nada, pero expone: un fichero compartido con quien no
99
+ debía ya ha salido, y eso no se deshace quitando el permiso después. Para
100
+ un agente que decide solo, pesa como un borrado. */
101
+ share_drive_file: 'destructiva',
102
+ /* Añadir un rol no borra nada, pero puede dar permisos de administrador. Es
103
+ el mismo peso que quitarlo, que ya era destructiva por su verbo. */
104
+ add_discord_role: 'destructiva',
105
+ });
106
+ /**
107
+ * La clase de una herramienta, o `null` si ningún verbo la reconoce.
108
+ *
109
+ * El `null` es para las pruebas: una herramienta nueva con un verbo que nadie
110
+ * conoce tiene que ponerlas en rojo, no colarse clasificada por descuido.
111
+ */
112
+ export function claseDeHerramienta(toolName) {
113
+ const nombre = (toolName ?? '').trim().toLowerCase();
114
+ if (!nombre)
115
+ return null;
116
+ const excepcion = EXCEPCIONES[nombre];
117
+ if (excepcion)
118
+ return excepcion;
119
+ return POR_VERBO[nombre.split('_')[0]] ?? null;
120
+ }
121
+ /**
122
+ * ¿Es destructiva? Lo que no se reconoce SE TRATA COMO DESTRUCTIVO: ante una
123
+ * herramienta que no sabemos qué hace, la respuesta segura para un gate es
124
+ * pararla, no dejarla pasar.
125
+ */
126
+ export function esHerramientaDestructiva(toolName) {
127
+ const clase = claseDeHerramienta(toolName);
128
+ return clase === null || clase === 'destructiva';
129
+ }
130
+ /**
131
+ * Pone el `destructive` de cada operación de un toolkit a partir de su nombre.
132
+ *
133
+ * Se llama UNA vez, al cargar el módulo de cada toolkit, sobre su propio
134
+ * array. Muta en sitio a propósito: los `*_BY_TOOL_NAME` y cualquier otro
135
+ * índice apuntan a los MISMOS objetos, así que ven el flag sin tener que
136
+ * reconstruirse. `true` cuando es destructiva; cuando no, la clave desaparece
137
+ * — un `destructive: false` explícito se leería como una decisión que nadie
138
+ * tomó.
139
+ */
140
+ export function marcarDestructivas(specs) {
141
+ for (const spec of specs) {
142
+ if (esHerramientaDestructiva(spec.toolName))
143
+ spec.destructive = true;
144
+ else
145
+ delete spec.destructive;
146
+ }
147
+ return specs;
148
+ }