@hostwebhook/node-types 1.65.0 → 1.67.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.
- package/dist/capacidad-de-credencial.d.ts +75 -0
- package/dist/capacidad-de-credencial.js +73 -0
- package/dist/discord-operations.d.ts +31 -0
- package/dist/discord-operations.js +55 -1
- package/dist/discord-toolkit.d.ts +64 -0
- package/dist/discord-toolkit.js +264 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +31 -2
- package/dist/slack-operations.d.ts +28 -0
- package/dist/slack-operations.js +43 -1
- package/dist/slack-toolkit.d.ts +60 -0
- package/dist/slack-toolkit.js +239 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qué puede hacer un nodo según el TIPO de credencial que lleva puesta.
|
|
3
|
+
*
|
|
4
|
+
* Hasta ahora un nodo ofrecía siempre todas sus operaciones, porque todas sus
|
|
5
|
+
* credenciales podían hacerlas todas. Con los webhooks deja de ser cierto, y de
|
|
6
|
+
* una forma muy desigual:
|
|
7
|
+
*
|
|
8
|
+
* - Un webhook de Discord ejecuta 4 de las 17 —publicar, y leer/editar/borrar
|
|
9
|
+
* **sus propios** mensajes—, siempre en el único canal para el que se creó.
|
|
10
|
+
* - Un webhook de Slack ejecuta **1 de 16**, y ni siquiera puede elegir el
|
|
11
|
+
* canal: lo fija quien instala la credencial.
|
|
12
|
+
*
|
|
13
|
+
* Esa desigualdad es la que decide la forma de este fichero. No basta con
|
|
14
|
+
* esconder operaciones: hay que esconder **campos dentro de** una operación,
|
|
15
|
+
* porque el destino deja de ser una pregunta. Por eso son dos listas y no una.
|
|
16
|
+
*
|
|
17
|
+
* ── Dónde vive esto y por qué ──
|
|
18
|
+
*
|
|
19
|
+
* Aquí, en el paquete, y no en cada consumidor. Lo mismo tienen que saberlo
|
|
20
|
+
* CUATRO sitios, y si cada uno lo deduce por su cuenta acaban discrepando sin
|
|
21
|
+
* que nada falle al compilar:
|
|
22
|
+
*
|
|
23
|
+
* 1. el selector de operación de la pantalla de detalle;
|
|
24
|
+
* 2. el renderizador de campos de esa misma pantalla;
|
|
25
|
+
* 3. la validación al guardar, en la api;
|
|
26
|
+
* 4. **la expansión del toolkit de IA** — que es el que se rompe más callado:
|
|
27
|
+
* sin esto, a un nodo con credencial de webhook se le entregan al modelo
|
|
28
|
+
* 17 herramientas de las que 13 fallan siempre.
|
|
29
|
+
*
|
|
30
|
+
* Es la misma razón por la que los toolkits se mudaron aquí en 1.66.0. Tener el
|
|
31
|
+
* dato dos veces es tenerlo mal a la vuelta de unas semanas.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Lo que una credencial concreta deja hacer.
|
|
35
|
+
*
|
|
36
|
+
* `operaciones` ausente significa «todas»: así el caso normal —un bot, que
|
|
37
|
+
* puede con todo— no necesita enumerarse ni mantenerse, y sólo se escribe lo
|
|
38
|
+
* que está ACOTADO. Añadir una operación nueva al nodo no obliga a tocar este
|
|
39
|
+
* fichero salvo que el webhook también pueda ejecutarla, que es justo la
|
|
40
|
+
* decisión que uno quiere tener que tomar a mano.
|
|
41
|
+
*/
|
|
42
|
+
export interface CapacidadDeCredencial<Op extends string = string> {
|
|
43
|
+
/** Las que puede ejecutar. Ausente = todas las del nodo. */
|
|
44
|
+
operaciones?: readonly Op[];
|
|
45
|
+
/**
|
|
46
|
+
* Campos que desaparecen del formulario y que la validación rechaza.
|
|
47
|
+
*
|
|
48
|
+
* Se juntan aquí dos motivos que para el usuario son el mismo —«esto no lo
|
|
49
|
+
* eliges tú»— aunque por dentro no lo sean: el que viene FIJADO por la
|
|
50
|
+
* credencial (el canal de un webhook) y el que el endpoint sencillamente NO
|
|
51
|
+
* ADMITE (responder a un mensaje: `Execute Webhook` no acepta
|
|
52
|
+
* `message_reference`). Separarlos daría dos listas que se consultan siempre
|
|
53
|
+
* juntas.
|
|
54
|
+
*/
|
|
55
|
+
camposNoDisponibles?: readonly string[];
|
|
56
|
+
}
|
|
57
|
+
/** Mapa de un proveedor: tipo de credencial → lo que deja hacer. */
|
|
58
|
+
export type CapacidadesPorCredencial<Op extends string = string> = Readonly<Record<string, CapacidadDeCredencial<Op>>>;
|
|
59
|
+
/**
|
|
60
|
+
* Qué operaciones ofrecer para esta credencial.
|
|
61
|
+
*
|
|
62
|
+
* Sin tipo de credencial —el nodo todavía no tiene ninguna elegida— se
|
|
63
|
+
* devuelven todas: es un hueco por rellenar, no una restricción, y esconder
|
|
64
|
+
* operaciones ahí haría creer que el nodo no sabe hacerlas.
|
|
65
|
+
*/
|
|
66
|
+
export declare function operacionesPara<Op extends string>(todas: readonly Op[], mapa: CapacidadesPorCredencial<Op>, tipoDeCredencial: string | null | undefined): readonly Op[];
|
|
67
|
+
/** Si esta credencial puede ejecutar esta operación. */
|
|
68
|
+
export declare function puedeEjecutar<Op extends string>(todas: readonly Op[], mapa: CapacidadesPorCredencial<Op>, tipoDeCredencial: string | null | undefined, operacion: Op): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Campos que esta credencial no deja elegir.
|
|
71
|
+
*
|
|
72
|
+
* Devuelve un `Set` porque quien lo usa siempre pregunta «¿este campo sí o
|
|
73
|
+
* no?», nunca recorre la lista.
|
|
74
|
+
*/
|
|
75
|
+
export declare function camposNoDisponiblesPara(mapa: CapacidadesPorCredencial, tipoDeCredencial: string | null | undefined): ReadonlySet<string>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Qué puede hacer un nodo según el TIPO de credencial que lleva puesta.
|
|
4
|
+
*
|
|
5
|
+
* Hasta ahora un nodo ofrecía siempre todas sus operaciones, porque todas sus
|
|
6
|
+
* credenciales podían hacerlas todas. Con los webhooks deja de ser cierto, y de
|
|
7
|
+
* una forma muy desigual:
|
|
8
|
+
*
|
|
9
|
+
* - Un webhook de Discord ejecuta 4 de las 17 —publicar, y leer/editar/borrar
|
|
10
|
+
* **sus propios** mensajes—, siempre en el único canal para el que se creó.
|
|
11
|
+
* - Un webhook de Slack ejecuta **1 de 16**, y ni siquiera puede elegir el
|
|
12
|
+
* canal: lo fija quien instala la credencial.
|
|
13
|
+
*
|
|
14
|
+
* Esa desigualdad es la que decide la forma de este fichero. No basta con
|
|
15
|
+
* esconder operaciones: hay que esconder **campos dentro de** una operación,
|
|
16
|
+
* porque el destino deja de ser una pregunta. Por eso son dos listas y no una.
|
|
17
|
+
*
|
|
18
|
+
* ── Dónde vive esto y por qué ──
|
|
19
|
+
*
|
|
20
|
+
* Aquí, en el paquete, y no en cada consumidor. Lo mismo tienen que saberlo
|
|
21
|
+
* CUATRO sitios, y si cada uno lo deduce por su cuenta acaban discrepando sin
|
|
22
|
+
* que nada falle al compilar:
|
|
23
|
+
*
|
|
24
|
+
* 1. el selector de operación de la pantalla de detalle;
|
|
25
|
+
* 2. el renderizador de campos de esa misma pantalla;
|
|
26
|
+
* 3. la validación al guardar, en la api;
|
|
27
|
+
* 4. **la expansión del toolkit de IA** — que es el que se rompe más callado:
|
|
28
|
+
* sin esto, a un nodo con credencial de webhook se le entregan al modelo
|
|
29
|
+
* 17 herramientas de las que 13 fallan siempre.
|
|
30
|
+
*
|
|
31
|
+
* Es la misma razón por la que los toolkits se mudaron aquí en 1.66.0. Tener el
|
|
32
|
+
* dato dos veces es tenerlo mal a la vuelta de unas semanas.
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.operacionesPara = operacionesPara;
|
|
36
|
+
exports.puedeEjecutar = puedeEjecutar;
|
|
37
|
+
exports.camposNoDisponiblesPara = camposNoDisponiblesPara;
|
|
38
|
+
/** Ninguna restricción: el caso del bot, y el de cualquier tipo sin entrada. */
|
|
39
|
+
const SIN_LIMITE = {};
|
|
40
|
+
/**
|
|
41
|
+
* Qué operaciones ofrecer para esta credencial.
|
|
42
|
+
*
|
|
43
|
+
* Sin tipo de credencial —el nodo todavía no tiene ninguna elegida— se
|
|
44
|
+
* devuelven todas: es un hueco por rellenar, no una restricción, y esconder
|
|
45
|
+
* operaciones ahí haría creer que el nodo no sabe hacerlas.
|
|
46
|
+
*/
|
|
47
|
+
function operacionesPara(todas, mapa, tipoDeCredencial) {
|
|
48
|
+
if (!tipoDeCredencial)
|
|
49
|
+
return todas;
|
|
50
|
+
const cap = mapa[tipoDeCredencial] ?? SIN_LIMITE;
|
|
51
|
+
if (!cap.operaciones)
|
|
52
|
+
return todas;
|
|
53
|
+
/* Se filtra `todas` en vez de devolver la lista del mapa para que el ORDEN
|
|
54
|
+
sea siempre el del nodo. Si no, la pantalla reordenaría sus operaciones al
|
|
55
|
+
cambiar de credencial, que se lee como si hubieran cambiado otras cosas. */
|
|
56
|
+
const permitidas = new Set(cap.operaciones);
|
|
57
|
+
return todas.filter((op) => permitidas.has(op));
|
|
58
|
+
}
|
|
59
|
+
/** Si esta credencial puede ejecutar esta operación. */
|
|
60
|
+
function puedeEjecutar(todas, mapa, tipoDeCredencial, operacion) {
|
|
61
|
+
return operacionesPara(todas, mapa, tipoDeCredencial).includes(operacion);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Campos que esta credencial no deja elegir.
|
|
65
|
+
*
|
|
66
|
+
* Devuelve un `Set` porque quien lo usa siempre pregunta «¿este campo sí o
|
|
67
|
+
* no?», nunca recorre la lista.
|
|
68
|
+
*/
|
|
69
|
+
function camposNoDisponiblesPara(mapa, tipoDeCredencial) {
|
|
70
|
+
if (!tipoDeCredencial)
|
|
71
|
+
return new Set();
|
|
72
|
+
return new Set(mapa[tipoDeCredencial]?.camposNoDisponibles ?? []);
|
|
73
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* forum-post create) extend this list — bump the package and re-install
|
|
18
18
|
* in consumers, same pattern as Gmail / Calendar / Drive / Telegram.
|
|
19
19
|
*/
|
|
20
|
+
import { type CapacidadesPorCredencial } from './capacidad-de-credencial';
|
|
20
21
|
export declare const DISCORD_OPERATIONS: readonly ["sendMessage", "editMessage", "deleteMessage", "getMessage", "addReaction", "removeReaction", "pinMessage", "createChannel", "editChannel", "deleteChannel", "getChannel", "listChannels", "createThread", "sendDM", "addRole", "removeRole", "getMember"];
|
|
21
22
|
export type DiscordOperation = (typeof DISCORD_OPERATIONS)[number];
|
|
22
23
|
/** Type guard — useful when validating untrusted input (DTOs, tool calls). */
|
|
@@ -71,3 +72,33 @@ export interface DiscordOperationSpec {
|
|
|
71
72
|
params: DiscordParamSpec[];
|
|
72
73
|
}
|
|
73
74
|
export declare const DISCORD_OPERATION_SPECS: Record<DiscordOperation, DiscordOperationSpec>;
|
|
75
|
+
/**
|
|
76
|
+
* Lo que puede hacer una credencial de Discord que NO es de bot.
|
|
77
|
+
*
|
|
78
|
+
* Medido contra la documentación de Discord, no de memoria:
|
|
79
|
+
* https://docs.discord.com/developers/resources/webhook
|
|
80
|
+
*
|
|
81
|
+
* `Execute Webhook` publica, y `Get/Edit/Delete Webhook Message` leen, editan y
|
|
82
|
+
* borran mensajes — pero **sólo los que envió ese mismo webhook**, y sólo en el
|
|
83
|
+
* canal para el que se creó. Las otras 13 (reacciones, pins, CRUD de canales,
|
|
84
|
+
* crear hilos, DMs, roles, miembros) piden un Bot Token: la propia doc las
|
|
85
|
+
* marca con `MANAGE_WEBHOOKS` o con permisos de guild.
|
|
86
|
+
*
|
|
87
|
+
* Los tres campos que se caen de `sendMessage`, uno por uno:
|
|
88
|
+
*
|
|
89
|
+
* - `channelId`: la URL del webhook YA dice el canal. El endpoint ni siquiera
|
|
90
|
+
* acepta `channel_id`.
|
|
91
|
+
* - `replyToMessageId`: `Execute Webhook` no acepta `message_reference`. No
|
|
92
|
+
* es que venga fijado — es que responder no existe por esta vía.
|
|
93
|
+
* - `interactionReply`: contestar a un slash command es cosa de la
|
|
94
|
+
* aplicación, no de un webhook.
|
|
95
|
+
*
|
|
96
|
+
* `threadId` SÍ se queda: `?thread_id=` es parámetro de query del endpoint.
|
|
97
|
+
*/
|
|
98
|
+
export declare const DISCORD_CAPACIDADES_POR_CREDENCIAL: CapacidadesPorCredencial<DiscordOperation>;
|
|
99
|
+
/** Las operaciones que ofrecer para una credencial de Discord. */
|
|
100
|
+
export declare function operacionesDeDiscordPara(tipoDeCredencial: string | null | undefined): readonly DiscordOperation[];
|
|
101
|
+
/** Si esa credencial puede ejecutar esa operación de Discord. */
|
|
102
|
+
export declare function discordPuedeEjecutar(tipoDeCredencial: string | null | undefined, operacion: DiscordOperation): boolean;
|
|
103
|
+
/** Los campos que esa credencial de Discord no deja elegir. */
|
|
104
|
+
export declare function camposDeDiscordNoDisponibles(tipoDeCredencial: string | null | undefined): ReadonlySet<string>;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = void 0;
|
|
3
|
+
exports.DISCORD_CAPACIDADES_POR_CREDENCIAL = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = void 0;
|
|
4
4
|
exports.isDiscordOperation = isDiscordOperation;
|
|
5
|
+
exports.operacionesDeDiscordPara = operacionesDeDiscordPara;
|
|
6
|
+
exports.discordPuedeEjecutar = discordPuedeEjecutar;
|
|
7
|
+
exports.camposDeDiscordNoDisponibles = camposDeDiscordNoDisponibles;
|
|
5
8
|
/**
|
|
6
9
|
* Discord Bot API operation enum — single source of truth across the
|
|
7
10
|
* API, Dashboard, and any future consumers (Message Broker, MCP server).
|
|
@@ -21,6 +24,7 @@ exports.isDiscordOperation = isDiscordOperation;
|
|
|
21
24
|
* forum-post create) extend this list — bump the package and re-install
|
|
22
25
|
* in consumers, same pattern as Gmail / Calendar / Drive / Telegram.
|
|
23
26
|
*/
|
|
27
|
+
const capacidad_de_credencial_1 = require("./capacidad-de-credencial");
|
|
24
28
|
exports.DISCORD_OPERATIONS = [
|
|
25
29
|
// Messages
|
|
26
30
|
'sendMessage',
|
|
@@ -358,3 +362,53 @@ exports.DISCORD_OPERATION_SPECS = {
|
|
|
358
362
|
params: [guildId(), userId()],
|
|
359
363
|
},
|
|
360
364
|
};
|
|
365
|
+
/**
|
|
366
|
+
* Lo que puede hacer una credencial de Discord que NO es de bot.
|
|
367
|
+
*
|
|
368
|
+
* Medido contra la documentación de Discord, no de memoria:
|
|
369
|
+
* https://docs.discord.com/developers/resources/webhook
|
|
370
|
+
*
|
|
371
|
+
* `Execute Webhook` publica, y `Get/Edit/Delete Webhook Message` leen, editan y
|
|
372
|
+
* borran mensajes — pero **sólo los que envió ese mismo webhook**, y sólo en el
|
|
373
|
+
* canal para el que se creó. Las otras 13 (reacciones, pins, CRUD de canales,
|
|
374
|
+
* crear hilos, DMs, roles, miembros) piden un Bot Token: la propia doc las
|
|
375
|
+
* marca con `MANAGE_WEBHOOKS` o con permisos de guild.
|
|
376
|
+
*
|
|
377
|
+
* Los tres campos que se caen de `sendMessage`, uno por uno:
|
|
378
|
+
*
|
|
379
|
+
* - `channelId`: la URL del webhook YA dice el canal. El endpoint ni siquiera
|
|
380
|
+
* acepta `channel_id`.
|
|
381
|
+
* - `replyToMessageId`: `Execute Webhook` no acepta `message_reference`. No
|
|
382
|
+
* es que venga fijado — es que responder no existe por esta vía.
|
|
383
|
+
* - `interactionReply`: contestar a un slash command es cosa de la
|
|
384
|
+
* aplicación, no de un webhook.
|
|
385
|
+
*
|
|
386
|
+
* `threadId` SÍ se queda: `?thread_id=` es parámetro de query del endpoint.
|
|
387
|
+
*/
|
|
388
|
+
exports.DISCORD_CAPACIDADES_POR_CREDENCIAL = {
|
|
389
|
+
discord_webhook: {
|
|
390
|
+
operaciones: [
|
|
391
|
+
'sendMessage',
|
|
392
|
+
'editMessage',
|
|
393
|
+
'deleteMessage',
|
|
394
|
+
'getMessage',
|
|
395
|
+
],
|
|
396
|
+
camposNoDisponibles: [
|
|
397
|
+
'channelId',
|
|
398
|
+
'replyToMessageId',
|
|
399
|
+
'interactionReply',
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
/** Las operaciones que ofrecer para una credencial de Discord. */
|
|
404
|
+
function operacionesDeDiscordPara(tipoDeCredencial) {
|
|
405
|
+
return (0, capacidad_de_credencial_1.operacionesPara)(exports.DISCORD_OPERATIONS, exports.DISCORD_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial);
|
|
406
|
+
}
|
|
407
|
+
/** Si esa credencial puede ejecutar esa operación de Discord. */
|
|
408
|
+
function discordPuedeEjecutar(tipoDeCredencial, operacion) {
|
|
409
|
+
return (0, capacidad_de_credencial_1.puedeEjecutar)(exports.DISCORD_OPERATIONS, exports.DISCORD_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial, operacion);
|
|
410
|
+
}
|
|
411
|
+
/** Los campos que esa credencial de Discord no deja elegir. */
|
|
412
|
+
function camposDeDiscordNoDisponibles(tipoDeCredencial) {
|
|
413
|
+
return (0, capacidad_de_credencial_1.camposNoDisponiblesPara)(exports.DISCORD_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial);
|
|
414
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord AI Toolkit — las herramientas que un discordAction expone cuando
|
|
3
|
+
* `aiEnabled` está encendido.
|
|
4
|
+
*
|
|
5
|
+
* **Se escribe una sola vez.** Antes vivía dos veces: una copia en
|
|
6
|
+
* `dashboard/components/discord-actions/discord-operations-schemas.ts` y otra en
|
|
7
|
+
* `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y en repos distintos,
|
|
8
|
+
* así que ningún test podía compararlas. Es la octava y novena mudanza de esta
|
|
9
|
+
* serie; de las siete anteriores, la de Contacts salió de que una copia
|
|
10
|
+
* anunciara 15 herramientas y expusiera una.
|
|
11
|
+
*
|
|
12
|
+
* Y sí había deriva, medida al mudar: la descripción de `addReaction` decía en
|
|
13
|
+
* el dashboard «URL-encoding is handled.» y en la api «URL-encoding is handled —
|
|
14
|
+
* pass the raw form.». Gana la de la api, que es la que el servidor MCP sirve
|
|
15
|
+
* hoy a clientes externos y la que dice qué hacer. Las 17 operaciones, sus
|
|
16
|
+
* nombres de herramienta y sus parámetros coincidían exactamente.
|
|
17
|
+
*
|
|
18
|
+
* Targets Discord API v10. Los parámetros snowflake son `string` porque los ids
|
|
19
|
+
* de Discord se pasan de la precisión de Number (17-20 dígitos).
|
|
20
|
+
*
|
|
21
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
|
|
22
|
+
* pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
|
|
23
|
+
* y quien mire la lista de herramientas del servidor MCP.
|
|
24
|
+
*/
|
|
25
|
+
import type { DiscordOperation } from './discord-operations';
|
|
26
|
+
export interface DiscordToolkitParameter {
|
|
27
|
+
name: string;
|
|
28
|
+
type: 'string' | 'number' | 'boolean';
|
|
29
|
+
description: string;
|
|
30
|
+
required: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface DiscordToolkitSpec {
|
|
33
|
+
operation: DiscordOperation;
|
|
34
|
+
/** Etiqueta corta de la fila en la lista de herramientas. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** Nombre con el que el LLM llama a la herramienta. */
|
|
37
|
+
toolName: string;
|
|
38
|
+
/** Descripción (más reglas de uso) que ve el LLM. */
|
|
39
|
+
description: string;
|
|
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. */
|
|
44
|
+
destructive?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare const DISCORD_TOOLKIT_SPECS: DiscordToolkitSpec[];
|
|
47
|
+
export declare const DISCORD_TOOLKIT_BY_TOOL_NAME: Record<string, DiscordToolkitSpec>;
|
|
48
|
+
/**
|
|
49
|
+
* Las herramientas que ofrecerle al modelo con ESTA credencial.
|
|
50
|
+
*
|
|
51
|
+
* Vive aquí y no en cada consumidor porque son dos —la expansión MCP de la api
|
|
52
|
+
* y el selector del AI Node del dashboard— y filtrar «casi igual» en dos sitios
|
|
53
|
+
* es exactamente la duplicación que costó tres PR quitar de este paquete.
|
|
54
|
+
*
|
|
55
|
+
* Filtra en los dos niveles, que es lo que hace falta y no es obvio:
|
|
56
|
+
*
|
|
57
|
+
* 1. **Operaciones**: sin esto, a un nodo con credencial de webhook se le
|
|
58
|
+
* entregan al modelo 17 herramientas de las que 13 fallan siempre. El
|
|
59
|
+
* modelo no tiene forma de saberlo: las ve anunciadas.
|
|
60
|
+
* 2. **Parámetros**: una herramienta que sigue pidiendo `channelId` invita al
|
|
61
|
+
* modelo a inventarse un canal que el webhook va a ignorar. Peor que
|
|
62
|
+
* fallar, porque parece que funcionó.
|
|
63
|
+
*/
|
|
64
|
+
export declare function herramientasDeDiscordPara(tipoDeCredencial: string | null | undefined): DiscordToolkitSpec[];
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Discord AI Toolkit — las herramientas que un discordAction expone cuando
|
|
4
|
+
* `aiEnabled` está encendido.
|
|
5
|
+
*
|
|
6
|
+
* **Se escribe una sola vez.** Antes vivía dos veces: una copia en
|
|
7
|
+
* `dashboard/components/discord-actions/discord-operations-schemas.ts` y otra en
|
|
8
|
+
* `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y en repos distintos,
|
|
9
|
+
* así que ningún test podía compararlas. Es la octava y novena mudanza de esta
|
|
10
|
+
* serie; de las siete anteriores, la de Contacts salió de que una copia
|
|
11
|
+
* anunciara 15 herramientas y expusiera una.
|
|
12
|
+
*
|
|
13
|
+
* Y sí había deriva, medida al mudar: la descripción de `addReaction` decía en
|
|
14
|
+
* el dashboard «URL-encoding is handled.» y en la api «URL-encoding is handled —
|
|
15
|
+
* pass the raw form.». Gana la de la api, que es la que el servidor MCP sirve
|
|
16
|
+
* hoy a clientes externos y la que dice qué hacer. Las 17 operaciones, sus
|
|
17
|
+
* nombres de herramienta y sus parámetros coincidían exactamente.
|
|
18
|
+
*
|
|
19
|
+
* Targets Discord API v10. Los parámetros snowflake son `string` porque los ids
|
|
20
|
+
* de Discord se pasan de la precisión de Number (17-20 dígitos).
|
|
21
|
+
*
|
|
22
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
|
|
23
|
+
* pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
|
|
24
|
+
* y quien mire la lista de herramientas del servidor MCP.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.DISCORD_TOOLKIT_BY_TOOL_NAME = exports.DISCORD_TOOLKIT_SPECS = void 0;
|
|
28
|
+
exports.herramientasDeDiscordPara = herramientasDeDiscordPara;
|
|
29
|
+
const discord_operations_1 = require("./discord-operations");
|
|
30
|
+
const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
|
|
31
|
+
exports.DISCORD_TOOLKIT_SPECS = [
|
|
32
|
+
// ── Messages ───────────────────────────────────────────────────
|
|
33
|
+
{
|
|
34
|
+
operation: 'sendMessage',
|
|
35
|
+
label: 'Send message',
|
|
36
|
+
toolName: 'send_discord_message',
|
|
37
|
+
description: 'Post a message to a Discord channel or thread. ' +
|
|
38
|
+
'USAGE RULES: ' +
|
|
39
|
+
'(1) `channelId` is the snowflake of a channel the bot can see. Get it from the trigger payload, listChannels, or the user. Never invent one. ' +
|
|
40
|
+
'(2) For a quote-style reply pointing at a specific message, set `replyToMessageId`. ' +
|
|
41
|
+
"(3) To post INTO a thread instead of the parent channel, set `threadId` to the thread's snowflake. " +
|
|
42
|
+
'(4) Discord allows up to 2000 chars per message and supports markdown.',
|
|
43
|
+
parameters: [
|
|
44
|
+
p('channelId', 'Channel snowflake (or thread snowflake to post into a thread).'),
|
|
45
|
+
p('content', 'Message text. Markdown is supported. Max 2000 chars.'),
|
|
46
|
+
p('replyToMessageId', 'Snowflake of a message to quote-reply to.', false),
|
|
47
|
+
p('threadId', 'Thread snowflake to post into (overrides channelId for the actual destination).', false),
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
operation: 'editMessage',
|
|
52
|
+
label: 'Edit message',
|
|
53
|
+
toolName: 'edit_discord_message',
|
|
54
|
+
description: "Edit an existing Discord message. The bot can only edit messages it sent itself — editing someone else's message will fail with 403.",
|
|
55
|
+
parameters: [
|
|
56
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
57
|
+
p('messageId', 'Snowflake of the message to edit.'),
|
|
58
|
+
p('content', 'New message content. Markdown supported. Max 2000 chars.'),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
operation: 'deleteMessage',
|
|
63
|
+
label: 'Delete message',
|
|
64
|
+
toolName: 'delete_discord_message',
|
|
65
|
+
description: "Permanently delete a Discord message. Irreversible. Deleting another user's message requires the bot to have Manage Messages permission. Always confirm with the user before calling this on user-authored messages.",
|
|
66
|
+
parameters: [
|
|
67
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
68
|
+
p('messageId', 'Snowflake of the message to delete.'),
|
|
69
|
+
],
|
|
70
|
+
destructive: true,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
operation: 'getMessage',
|
|
74
|
+
label: 'Get message',
|
|
75
|
+
toolName: 'get_discord_message',
|
|
76
|
+
description: 'Fetch a single Discord message with its content, author, embeds, attachments, reactions, and pinned state. Use BEFORE editing or reacting to verify the message still exists.',
|
|
77
|
+
parameters: [
|
|
78
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
79
|
+
p('messageId', 'Snowflake of the message to fetch.'),
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
operation: 'addReaction',
|
|
84
|
+
label: 'Add reaction',
|
|
85
|
+
toolName: 'add_discord_reaction',
|
|
86
|
+
description: 'React to a message as the bot. ' +
|
|
87
|
+
'EMOJI FORMAT: ' +
|
|
88
|
+
'(1) Unicode emoji — pass the character itself (e.g. `🔥`, `👍`). ' +
|
|
89
|
+
'(2) Custom server emoji — pass `name:id` (e.g. `partyparrot:123456789012345678`). ' +
|
|
90
|
+
'URL-encoding is handled — pass the raw form. The bot must be in the server and have Add Reactions permission.',
|
|
91
|
+
parameters: [
|
|
92
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
93
|
+
p('messageId', 'Snowflake of the message to react to.'),
|
|
94
|
+
p('emoji', 'Unicode emoji or `name:id` for custom emoji.'),
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
operation: 'removeReaction',
|
|
99
|
+
label: 'Remove reaction',
|
|
100
|
+
toolName: 'remove_discord_reaction',
|
|
101
|
+
description: "Remove the bot's own reaction from a message. Same emoji format as add_discord_reaction. Cannot remove other users' reactions through this tool.",
|
|
102
|
+
parameters: [
|
|
103
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
104
|
+
p('messageId', 'Snowflake of the reacted message.'),
|
|
105
|
+
p('emoji', 'Unicode emoji or `name:id` for custom emoji.'),
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
operation: 'pinMessage',
|
|
110
|
+
label: 'Pin message',
|
|
111
|
+
toolName: 'pin_discord_message',
|
|
112
|
+
description: 'Pin a message in its channel. The bot must have Manage Messages permission. Each channel has a 50-pin limit; pinning a 51st message returns 403.',
|
|
113
|
+
parameters: [
|
|
114
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
115
|
+
p('messageId', 'Snowflake of the message to pin.'),
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
// ── Channels ───────────────────────────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
operation: 'createChannel',
|
|
121
|
+
label: 'Create channel',
|
|
122
|
+
toolName: 'create_discord_channel',
|
|
123
|
+
description: 'Create a new channel in a Discord guild. The bot must have Manage Channels permission. ' +
|
|
124
|
+
'TYPES: `text` (default), `voice`, `forum`, `announcement`, `category`. ' +
|
|
125
|
+
"To put the new channel under a category, set `parentId` to the category's snowflake.",
|
|
126
|
+
parameters: [
|
|
127
|
+
p('guildId', 'Guild (server) snowflake.'),
|
|
128
|
+
p('name', 'Channel name. Lowercase / hyphens recommended for text channels.'),
|
|
129
|
+
p('type', 'Channel type: text, voice, forum, announcement, category.', false),
|
|
130
|
+
p('parentId', 'Snowflake of the parent category. Optional.', false),
|
|
131
|
+
p('topic', 'Channel description (text channels only).', false),
|
|
132
|
+
p('slowmode', 'Per-user message rate limit in seconds (0-21600).', false, 'number'),
|
|
133
|
+
p('nsfw', 'Mark channel as age-restricted.', false, 'boolean'),
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
operation: 'editChannel',
|
|
138
|
+
label: 'Edit channel',
|
|
139
|
+
toolName: 'edit_discord_channel',
|
|
140
|
+
description: 'Rename, change topic, slowmode, or move a channel under a different category. Pass only fields you want changed. Bot needs Manage Channels.',
|
|
141
|
+
parameters: [
|
|
142
|
+
p('channelId', 'Channel snowflake.'),
|
|
143
|
+
p('name', 'New channel name.', false),
|
|
144
|
+
p('topic', 'New channel description.', false),
|
|
145
|
+
p('slowmode', 'New slowmode in seconds (0-21600).', false, 'number'),
|
|
146
|
+
p('parentId', 'Move under a different category by snowflake.', false),
|
|
147
|
+
p('nsfw', 'Toggle age-restricted.', false, 'boolean'),
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
operation: 'deleteChannel',
|
|
152
|
+
label: 'Delete channel',
|
|
153
|
+
toolName: 'delete_discord_channel',
|
|
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
|
+
parameters: [p('channelId', 'Channel snowflake to delete.')],
|
|
156
|
+
destructive: true,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
operation: 'getChannel',
|
|
160
|
+
label: 'Get channel',
|
|
161
|
+
toolName: 'get_discord_channel',
|
|
162
|
+
description: "Fetch a Discord channel's full configuration: name, type, topic, parentId, NSFW flag, slowmode. Use to inspect before editing.",
|
|
163
|
+
parameters: [p('channelId', 'Channel snowflake to fetch.')],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
operation: 'listChannels',
|
|
167
|
+
label: 'List channels',
|
|
168
|
+
toolName: 'list_discord_channels',
|
|
169
|
+
description: 'List every channel in a guild — text, voice, categories, threads, forums. Returns an iterable array. Use this to FIND a channel by name before posting.',
|
|
170
|
+
parameters: [p('guildId', 'Guild snowflake.')],
|
|
171
|
+
},
|
|
172
|
+
// ── Threads ────────────────────────────────────────────────────
|
|
173
|
+
{
|
|
174
|
+
operation: 'createThread',
|
|
175
|
+
label: 'Create thread',
|
|
176
|
+
toolName: 'create_discord_thread',
|
|
177
|
+
description: 'Create a thread in a channel. ' +
|
|
178
|
+
'TWO MODES: ' +
|
|
179
|
+
'(1) With `messageId` — spawns the thread off that message. Inherits its visibility. ' +
|
|
180
|
+
'(2) Without `messageId` — standalone thread. Pass `type: public|private|announcement`. Private threads require Manage Threads permission.',
|
|
181
|
+
parameters: [
|
|
182
|
+
p('channelId', 'Parent channel snowflake.'),
|
|
183
|
+
p('name', 'Thread name.'),
|
|
184
|
+
p('messageId', 'Optional anchor message snowflake. When set, thread spawns off it.', false),
|
|
185
|
+
p('type', 'Thread type: public, private, announcement (ignored when messageId is set).', false),
|
|
186
|
+
p('autoArchiveMinutes', 'Auto-archive after N minutes of inactivity (60 / 1440 / 4320 / 10080).', false, 'number'),
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
// ── DMs ────────────────────────────────────────────────────────
|
|
190
|
+
{
|
|
191
|
+
operation: 'sendDM',
|
|
192
|
+
label: 'Send DM',
|
|
193
|
+
toolName: 'send_discord_dm',
|
|
194
|
+
description: "Direct-message a user. The user must share at least one server with the bot AND have DMs from server members enabled. Discord will silently drop the DM if the user has DMs disabled — that is the user's privacy choice. Do not retry.",
|
|
195
|
+
parameters: [
|
|
196
|
+
p('userId', 'User snowflake.'),
|
|
197
|
+
p('content', 'DM content. Markdown supported. Max 2000 chars.'),
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
// ── Roles & members ────────────────────────────────────────────
|
|
201
|
+
{
|
|
202
|
+
operation: 'addRole',
|
|
203
|
+
label: 'Add role to member',
|
|
204
|
+
toolName: 'add_discord_role',
|
|
205
|
+
description: 'Grant a role to a guild member. ' +
|
|
206
|
+
'CRITICAL: the bot\'s OWN role must be ABOVE the role being granted in the server\'s role hierarchy. A 403 with "Missing Permissions" almost always means the bot needs to be moved up — tell the user to drag the bot role above the target role in Server Settings → Roles.',
|
|
207
|
+
parameters: [
|
|
208
|
+
p('guildId', 'Guild snowflake.'),
|
|
209
|
+
p('userId', 'Member snowflake.'),
|
|
210
|
+
p('roleId', 'Role snowflake to grant.'),
|
|
211
|
+
],
|
|
212
|
+
destructive: true,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
operation: 'removeRole',
|
|
216
|
+
label: 'Remove role from member',
|
|
217
|
+
toolName: 'remove_discord_role',
|
|
218
|
+
description: "Remove a role from a guild member. Same hierarchy rule as add_discord_role — bot's role must be above the target role. Cannot remove the @everyone role.",
|
|
219
|
+
parameters: [
|
|
220
|
+
p('guildId', 'Guild snowflake.'),
|
|
221
|
+
p('userId', 'Member snowflake.'),
|
|
222
|
+
p('roleId', 'Role snowflake to remove.'),
|
|
223
|
+
],
|
|
224
|
+
destructive: true,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
operation: 'getMember',
|
|
228
|
+
label: 'Get member',
|
|
229
|
+
toolName: 'get_discord_member',
|
|
230
|
+
description: 'Fetch a guild member: their server nickname, role IDs, joined-at timestamp, and the underlying user object. Use BEFORE addRole / removeRole to inspect current roles.',
|
|
231
|
+
parameters: [
|
|
232
|
+
p('guildId', 'Guild snowflake.'),
|
|
233
|
+
p('userId', 'Member snowflake.'),
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
exports.DISCORD_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.DISCORD_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
|
|
238
|
+
/**
|
|
239
|
+
* Las herramientas que ofrecerle al modelo con ESTA credencial.
|
|
240
|
+
*
|
|
241
|
+
* Vive aquí y no en cada consumidor porque son dos —la expansión MCP de la api
|
|
242
|
+
* y el selector del AI Node del dashboard— y filtrar «casi igual» en dos sitios
|
|
243
|
+
* es exactamente la duplicación que costó tres PR quitar de este paquete.
|
|
244
|
+
*
|
|
245
|
+
* Filtra en los dos niveles, que es lo que hace falta y no es obvio:
|
|
246
|
+
*
|
|
247
|
+
* 1. **Operaciones**: sin esto, a un nodo con credencial de webhook se le
|
|
248
|
+
* entregan al modelo 17 herramientas de las que 13 fallan siempre. El
|
|
249
|
+
* modelo no tiene forma de saberlo: las ve anunciadas.
|
|
250
|
+
* 2. **Parámetros**: una herramienta que sigue pidiendo `channelId` invita al
|
|
251
|
+
* modelo a inventarse un canal que el webhook va a ignorar. Peor que
|
|
252
|
+
* fallar, porque parece que funcionó.
|
|
253
|
+
*/
|
|
254
|
+
function herramientasDeDiscordPara(tipoDeCredencial) {
|
|
255
|
+
const permitidas = new Set((0, discord_operations_1.operacionesDeDiscordPara)(tipoDeCredencial));
|
|
256
|
+
const fuera = (0, discord_operations_1.camposDeDiscordNoDisponibles)(tipoDeCredencial);
|
|
257
|
+
return exports.DISCORD_TOOLKIT_SPECS.filter((s) => permitidas.has(s.operation)).map((s) =>
|
|
258
|
+
/* Se devuelve una copia sólo cuando hay algo que quitar: así el camino
|
|
259
|
+
normal —el bot, que puede con todo— sigue entregando las MISMAS
|
|
260
|
+
referencias de siempre y nadie paga por una función que no le afecta. */
|
|
261
|
+
fuera.size === 0
|
|
262
|
+
? s
|
|
263
|
+
: { ...s, parameters: s.parameters.filter((p) => !fuera.has(p.name)) });
|
|
264
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,8 +22,12 @@ export { TELEGRAM_TOOLKIT_SPECS, TELEGRAM_TOOLKIT_BY_TOOL_NAME, } from './telegr
|
|
|
22
22
|
export type { TelegramToolkitSpec, TelegramToolkitParameter, } from './telegram-toolkit';
|
|
23
23
|
export { WHATSAPP_OPERATIONS, isWhatsAppOperation, } from './whatsapp-operations';
|
|
24
24
|
export type { WhatsAppOperation } from './whatsapp-operations';
|
|
25
|
-
export {
|
|
25
|
+
export { operacionesPara, puedeEjecutar, camposNoDisponiblesPara, } from './capacidad-de-credencial';
|
|
26
|
+
export type { CapacidadDeCredencial, CapacidadesPorCredencial, } from './capacidad-de-credencial';
|
|
27
|
+
export { DISCORD_OPERATIONS, DISCORD_OPERATION_SPECS, DISCORD_CAPACIDADES_POR_CREDENCIAL, operacionesDeDiscordPara, discordPuedeEjecutar, camposDeDiscordNoDisponibles, isDiscordOperation, } from './discord-operations';
|
|
26
28
|
export type { DiscordOperation, DiscordParamSpec, DiscordOperationSpec, } from './discord-operations';
|
|
29
|
+
export { DISCORD_TOOLKIT_SPECS, DISCORD_TOOLKIT_BY_TOOL_NAME, herramientasDeDiscordPara, } from './discord-toolkit';
|
|
30
|
+
export type { DiscordToolkitSpec, DiscordToolkitParameter, } from './discord-toolkit';
|
|
27
31
|
export { MAILCHIMP_OPERATIONS, MAILCHIMP_OPERATION_SPECS, MAILCHIMP_CONTACT_STATUSES, isMailchimpOperation, } from './mailchimp-operations';
|
|
28
32
|
export type { MailchimpOperation, MailchimpContactStatus, MailchimpParamSpec, MailchimpOperationSpec, } from './mailchimp-operations';
|
|
29
33
|
export { SHOPIFY_OPERATIONS, SHOPIFY_OPERATION_SPECS, SHOPIFY_TAGGABLE_RESOURCES, SHOPIFY_SEARCHABLE_RESOURCES, isShopifyOperation, } from './shopify-operations';
|
|
@@ -32,8 +36,10 @@ export { GITHUB_OPERATIONS, GITHUB_OPERATION_SPECS, GITHUB_DROPDOWN_OPERATIONS,
|
|
|
32
36
|
export type { GithubOperation, GithubParamType, GithubParamSpec, GithubOperationSpec, } from './github-operations';
|
|
33
37
|
export { JIRA_OPERATIONS, JIRA_OPERATION_SPECS, JIRA_DROPDOWN_OPERATIONS, JIRA_ITERABLE_OPERATIONS, isJiraOperation, } from './jira-operations';
|
|
34
38
|
export type { JiraOperation, JiraParamType, JiraParamSpec, JiraOperationSpec, } from './jira-operations';
|
|
35
|
-
export { SLACK_OPERATIONS, SLACK_OPERATION_SPECS, isSlackOperation, } from './slack-operations';
|
|
39
|
+
export { SLACK_OPERATIONS, SLACK_OPERATION_SPECS, SLACK_CAPACIDADES_POR_CREDENCIAL, operacionesDeSlackPara, slackPuedeEjecutar, camposDeSlackNoDisponibles, isSlackOperation, } from './slack-operations';
|
|
36
40
|
export type { SlackOperation, SlackParamSpec, SlackOperationSpec, } from './slack-operations';
|
|
41
|
+
export { SLACK_TOOLKIT_SPECS, SLACK_TOOLKIT_BY_TOOL_NAME, herramientasDeSlackPara, } from './slack-toolkit';
|
|
42
|
+
export type { SlackToolkitSpec, SlackToolkitParameter, } from './slack-toolkit';
|
|
37
43
|
export { SHEETS_OPERATIONS, SHEETS_OPERATION_SPECS, isSheetsOperation, } from './sheets-operations';
|
|
38
44
|
export type { SheetsOperation, SheetsParamSpec, SheetsOperationSpec, } from './sheets-operations';
|
|
39
45
|
export { SHEETS_TOOLKIT_SPECS, SHEETS_TOOLKIT_BY_TOOL_NAME, SHEETS_TOOLKIT_DEFAULTABLE, } from './sheets-toolkit';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DRIVE_OPERATION_SPECS = exports.DRIVE_OPERATIONS = exports.GOOGLE_CALENDAR_TOOLKIT_BY_TOOL_NAME = exports.GOOGLE_CALENDAR_TOOLKIT_SPECS = exports.isGoogleCalendarOperation = exports.GOOGLE_CALENDAR_OPERATION_SPECS = exports.GOOGLE_CALENDAR_OPERATIONS = exports.isGmailOperation = exports.resolveGmailSendFields = exports.GMAIL_SEND_LEGACY_FIELDS = exports.NATIVE_EMAIL_TOOLKIT_BY_TOOL_NAME = exports.GMAIL_TOOLKIT_BY_TOOL_NAME = exports.NATIVE_EMAIL_TOOLKIT_SPECS = exports.GMAIL_SEND_AND_WAIT_TOOL_SPEC = exports.GMAIL_ALL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_SPECS = exports.GMAIL_TOOLKIT_OPERATIONS = exports.GMAIL_DROPDOWN_OPERATIONS = exports.GMAIL_OPERATION_GROUPS = exports.GMAIL_OPERATION_SPECS = exports.GMAIL_OPERATIONS = exports.versionCatalogErrors = exports.fieldsLost = exports.fieldsLostBetween = exports.currentVersion = exports.versionSpec = exports.versionsOf = exports.isVersioned = exports.NODE_TYPE_TO_PREFIX = exports.PREFIX_TO_NODE_TYPE = exports.NODE_STATE_KEYS = exports.NODE_COLORS = exports.NODE_DETAIL_PATHS = exports.getNodeRegistryEntry = exports.NODE_REGISTRY = exports.getNodeDispatchConfig = exports.getAllNodeCollections = exports.NODE_DISPATCH = exports.resolveNodeId = exports.PREFIX_TO_TYPE = exports.NODE_UI = exports.ALL_NODE_TYPES = exports.isNodeType = exports.isTerminal = exports.canSendToNodes = exports.canReceiveFromNodes = exports.canReceiveFrom = exports.NODE_CONNECTIONS = exports.iterableMeta = exports.singleMeta = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = void 0;
|
|
4
|
+
exports.SLACK_TOOLKIT_SPECS = exports.isSlackOperation = exports.camposDeSlackNoDisponibles = exports.slackPuedeEjecutar = exports.operacionesDeSlackPara = exports.SLACK_CAPACIDADES_POR_CREDENCIAL = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = exports.isJiraOperation = exports.JIRA_ITERABLE_OPERATIONS = exports.JIRA_DROPDOWN_OPERATIONS = exports.JIRA_OPERATION_SPECS = exports.JIRA_OPERATIONS = exports.isGithubOperation = exports.GITHUB_ITERABLE_OPERATIONS = exports.GITHUB_DROPDOWN_OPERATIONS = exports.GITHUB_OPERATION_SPECS = exports.GITHUB_OPERATIONS = exports.isShopifyOperation = exports.SHOPIFY_SEARCHABLE_RESOURCES = exports.SHOPIFY_TAGGABLE_RESOURCES = exports.SHOPIFY_OPERATION_SPECS = exports.SHOPIFY_OPERATIONS = exports.isMailchimpOperation = exports.MAILCHIMP_CONTACT_STATUSES = exports.MAILCHIMP_OPERATION_SPECS = exports.MAILCHIMP_OPERATIONS = exports.herramientasDeDiscordPara = exports.DISCORD_TOOLKIT_BY_TOOL_NAME = exports.DISCORD_TOOLKIT_SPECS = exports.isDiscordOperation = exports.camposDeDiscordNoDisponibles = exports.discordPuedeEjecutar = exports.operacionesDeDiscordPara = exports.DISCORD_CAPACIDADES_POR_CREDENCIAL = exports.DISCORD_OPERATION_SPECS = exports.DISCORD_OPERATIONS = exports.camposNoDisponiblesPara = exports.puedeEjecutar = exports.operacionesPara = exports.isWhatsAppOperation = exports.WHATSAPP_OPERATIONS = exports.TELEGRAM_TOOLKIT_BY_TOOL_NAME = exports.TELEGRAM_TOOLKIT_SPECS = exports.isTelegramOperation = exports.TELEGRAM_OPERATION_SPECS = exports.TELEGRAM_OPERATIONS = exports.DRIVE_TOOLKIT_BY_TOOL_NAME = exports.DRIVE_TOOLKIT_SPECS = exports.isDriveOperation = void 0;
|
|
5
|
+
exports.isCredentialType = exports.getCredentialType = exports.credentialTypeValues = exports.CREDENTIAL_TYPE_VALUES = exports.CREDENTIAL_TYPES = exports.getModelLabel = exports.getDefaultModel = exports.getModelsFor = exports.MODEL_CONTEXT_WINDOWS = exports.LLM_MODELS = exports.LLM_PROVIDERS = exports.DOCS_TOOLKIT_DEFAULTABLE = exports.DOCS_TOOLKIT_BY_TOOL_NAME = exports.DOCS_TOOLKIT_SPECS = exports.isDocsOperation = exports.DOCS_OPERATION_SPECS = exports.DOCS_OPERATIONS = exports.isMongoOperation = exports.MONGO_OPERATION_SPECS = exports.MONGO_OPERATIONS = exports.isPostgresOperation = exports.POSTGRES_OPERATION_SPECS = exports.POSTGRES_MODES = exports.POSTGRES_OPERATIONS = exports.isNotionOperation = exports.NOTION_DROPDOWN_OPERATIONS = exports.NOTION_OPERATION_SPECS = exports.NOTION_OPERATIONS = exports.isGoogleAnalyticsOperation = exports.GOOGLE_ANALYTICS_DROPDOWN_OPERATIONS = exports.GOOGLE_ANALYTICS_OPERATION_SPECS = exports.GOOGLE_ANALYTICS_OPERATIONS = exports.isGoogleContactsOperation = exports.GOOGLE_CONTACTS_DEFAULT_PERSON_FIELDS = exports.GOOGLE_CONTACTS_OPERATION_GROUPS = exports.GOOGLE_CONTACTS_OPERATION_SPECS = exports.GOOGLE_CONTACTS_OPERATIONS_V2 = exports.GOOGLE_CONTACTS_OPERATIONS_V1 = exports.GOOGLE_CONTACTS_OPERATIONS = exports.SHEETS_TOOLKIT_DEFAULTABLE = exports.SHEETS_TOOLKIT_BY_TOOL_NAME = exports.SHEETS_TOOLKIT_SPECS = exports.isSheetsOperation = exports.SHEETS_OPERATION_SPECS = exports.SHEETS_OPERATIONS = exports.herramientasDeSlackPara = exports.SLACK_TOOLKIT_BY_TOOL_NAME = void 0;
|
|
6
6
|
var types_1 = require("./types");
|
|
7
7
|
Object.defineProperty(exports, "singleMeta", { enumerable: true, get: function () { return types_1.singleMeta; } });
|
|
8
8
|
Object.defineProperty(exports, "iterableMeta", { enumerable: true, get: function () { return types_1.iterableMeta; } });
|
|
@@ -83,10 +83,29 @@ Object.defineProperty(exports, "TELEGRAM_TOOLKIT_BY_TOOL_NAME", { enumerable: tr
|
|
|
83
83
|
var whatsapp_operations_1 = require("./whatsapp-operations");
|
|
84
84
|
Object.defineProperty(exports, "WHATSAPP_OPERATIONS", { enumerable: true, get: function () { return whatsapp_operations_1.WHATSAPP_OPERATIONS; } });
|
|
85
85
|
Object.defineProperty(exports, "isWhatsAppOperation", { enumerable: true, get: function () { return whatsapp_operations_1.isWhatsAppOperation; } });
|
|
86
|
+
/* La forma del mapa de capacidades. Los datos de cada proveedor viven en su
|
|
87
|
+
propio fichero de operaciones, que es donde se mira cuando uno se pregunta
|
|
88
|
+
qué sabe hacer ese nodo. */
|
|
89
|
+
var capacidad_de_credencial_1 = require("./capacidad-de-credencial");
|
|
90
|
+
Object.defineProperty(exports, "operacionesPara", { enumerable: true, get: function () { return capacidad_de_credencial_1.operacionesPara; } });
|
|
91
|
+
Object.defineProperty(exports, "puedeEjecutar", { enumerable: true, get: function () { return capacidad_de_credencial_1.puedeEjecutar; } });
|
|
92
|
+
Object.defineProperty(exports, "camposNoDisponiblesPara", { enumerable: true, get: function () { return capacidad_de_credencial_1.camposNoDisponiblesPara; } });
|
|
86
93
|
var discord_operations_1 = require("./discord-operations");
|
|
87
94
|
Object.defineProperty(exports, "DISCORD_OPERATIONS", { enumerable: true, get: function () { return discord_operations_1.DISCORD_OPERATIONS; } });
|
|
88
95
|
Object.defineProperty(exports, "DISCORD_OPERATION_SPECS", { enumerable: true, get: function () { return discord_operations_1.DISCORD_OPERATION_SPECS; } });
|
|
96
|
+
Object.defineProperty(exports, "DISCORD_CAPACIDADES_POR_CREDENCIAL", { enumerable: true, get: function () { return discord_operations_1.DISCORD_CAPACIDADES_POR_CREDENCIAL; } });
|
|
97
|
+
Object.defineProperty(exports, "operacionesDeDiscordPara", { enumerable: true, get: function () { return discord_operations_1.operacionesDeDiscordPara; } });
|
|
98
|
+
Object.defineProperty(exports, "discordPuedeEjecutar", { enumerable: true, get: function () { return discord_operations_1.discordPuedeEjecutar; } });
|
|
99
|
+
Object.defineProperty(exports, "camposDeDiscordNoDisponibles", { enumerable: true, get: function () { return discord_operations_1.camposDeDiscordNoDisponibles; } });
|
|
89
100
|
Object.defineProperty(exports, "isDiscordOperation", { enumerable: true, get: function () { return discord_operations_1.isDiscordOperation; } });
|
|
101
|
+
/* Ojo con los dos nombres parecidos, que vienen de dos ficheros y NO son lo
|
|
102
|
+
mismo: `DiscordOperationSpec` (arriba) describe el FORMULARIO —qué control
|
|
103
|
+
pintar por campo— y `DiscordToolkitSpec` (abajo) describe la HERRAMIENTA que
|
|
104
|
+
ve el LLM. Es la misma pareja que ya tienen Telegram, Sheets y compañía. */
|
|
105
|
+
var discord_toolkit_1 = require("./discord-toolkit");
|
|
106
|
+
Object.defineProperty(exports, "DISCORD_TOOLKIT_SPECS", { enumerable: true, get: function () { return discord_toolkit_1.DISCORD_TOOLKIT_SPECS; } });
|
|
107
|
+
Object.defineProperty(exports, "DISCORD_TOOLKIT_BY_TOOL_NAME", { enumerable: true, get: function () { return discord_toolkit_1.DISCORD_TOOLKIT_BY_TOOL_NAME; } });
|
|
108
|
+
Object.defineProperty(exports, "herramientasDeDiscordPara", { enumerable: true, get: function () { return discord_toolkit_1.herramientasDeDiscordPara; } });
|
|
90
109
|
var mailchimp_operations_1 = require("./mailchimp-operations");
|
|
91
110
|
Object.defineProperty(exports, "MAILCHIMP_OPERATIONS", { enumerable: true, get: function () { return mailchimp_operations_1.MAILCHIMP_OPERATIONS; } });
|
|
92
111
|
Object.defineProperty(exports, "MAILCHIMP_OPERATION_SPECS", { enumerable: true, get: function () { return mailchimp_operations_1.MAILCHIMP_OPERATION_SPECS; } });
|
|
@@ -113,7 +132,17 @@ Object.defineProperty(exports, "isJiraOperation", { enumerable: true, get: funct
|
|
|
113
132
|
var slack_operations_1 = require("./slack-operations");
|
|
114
133
|
Object.defineProperty(exports, "SLACK_OPERATIONS", { enumerable: true, get: function () { return slack_operations_1.SLACK_OPERATIONS; } });
|
|
115
134
|
Object.defineProperty(exports, "SLACK_OPERATION_SPECS", { enumerable: true, get: function () { return slack_operations_1.SLACK_OPERATION_SPECS; } });
|
|
135
|
+
Object.defineProperty(exports, "SLACK_CAPACIDADES_POR_CREDENCIAL", { enumerable: true, get: function () { return slack_operations_1.SLACK_CAPACIDADES_POR_CREDENCIAL; } });
|
|
136
|
+
Object.defineProperty(exports, "operacionesDeSlackPara", { enumerable: true, get: function () { return slack_operations_1.operacionesDeSlackPara; } });
|
|
137
|
+
Object.defineProperty(exports, "slackPuedeEjecutar", { enumerable: true, get: function () { return slack_operations_1.slackPuedeEjecutar; } });
|
|
138
|
+
Object.defineProperty(exports, "camposDeSlackNoDisponibles", { enumerable: true, get: function () { return slack_operations_1.camposDeSlackNoDisponibles; } });
|
|
116
139
|
Object.defineProperty(exports, "isSlackOperation", { enumerable: true, get: function () { return slack_operations_1.isSlackOperation; } });
|
|
140
|
+
/* Misma pareja que en Discord: `SlackOperationSpec` es el formulario,
|
|
141
|
+
`SlackToolkitSpec` es la herramienta que ve el LLM. */
|
|
142
|
+
var slack_toolkit_1 = require("./slack-toolkit");
|
|
143
|
+
Object.defineProperty(exports, "SLACK_TOOLKIT_SPECS", { enumerable: true, get: function () { return slack_toolkit_1.SLACK_TOOLKIT_SPECS; } });
|
|
144
|
+
Object.defineProperty(exports, "SLACK_TOOLKIT_BY_TOOL_NAME", { enumerable: true, get: function () { return slack_toolkit_1.SLACK_TOOLKIT_BY_TOOL_NAME; } });
|
|
145
|
+
Object.defineProperty(exports, "herramientasDeSlackPara", { enumerable: true, get: function () { return slack_toolkit_1.herramientasDeSlackPara; } });
|
|
117
146
|
var sheets_operations_1 = require("./sheets-operations");
|
|
118
147
|
Object.defineProperty(exports, "SHEETS_OPERATIONS", { enumerable: true, get: function () { return sheets_operations_1.SHEETS_OPERATIONS; } });
|
|
119
148
|
Object.defineProperty(exports, "SHEETS_OPERATION_SPECS", { enumerable: true, get: function () { return sheets_operations_1.SHEETS_OPERATION_SPECS; } });
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* publish, scheduled.list, etc.) extend this list — bump the package and
|
|
12
12
|
* re-install in consumers, same pattern as Gmail / Calendar / Discord.
|
|
13
13
|
*/
|
|
14
|
+
import { type CapacidadesPorCredencial } from './capacidad-de-credencial';
|
|
14
15
|
export declare const SLACK_OPERATIONS: readonly ["sendMessage", "updateMessage", "deleteMessage", "sendEphemeral", "scheduleMessage", "getMessage", "listChannels", "getChannelHistory", "getThreadReplies", "createChannel", "inviteToChannel", "addReaction", "removeReaction", "listUsers", "getUserInfo", "uploadFile"];
|
|
15
16
|
export type SlackOperation = (typeof SLACK_OPERATIONS)[number];
|
|
16
17
|
/** Type guard — useful when validating untrusted input (DTOs, AI tool calls). */
|
|
@@ -51,3 +52,30 @@ export interface SlackOperationSpec {
|
|
|
51
52
|
params: SlackParamSpec[];
|
|
52
53
|
}
|
|
53
54
|
export declare const SLACK_OPERATION_SPECS: Record<SlackOperation, SlackOperationSpec>;
|
|
55
|
+
/**
|
|
56
|
+
* Lo que puede hacer una credencial de Slack que NO es OAuth.
|
|
57
|
+
*
|
|
58
|
+
* Medido contra la documentación de Slack, no de memoria:
|
|
59
|
+
* https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks
|
|
60
|
+
*
|
|
61
|
+
* Es el caso extremo, y conviene que se lea así de crudo: **una** operación de
|
|
62
|
+
* las 16. Un incoming webhook publica y nada más. Textualmente: «Incoming
|
|
63
|
+
* webhooks do not allow you to delete a message after it's been posted», no
|
|
64
|
+
* lee, no sube ficheros, y editar exige `chat.update`, que es token.
|
|
65
|
+
*
|
|
66
|
+
* Y el campo que se cae es el que más duele, porque no parece una restricción
|
|
67
|
+
* hasta que lo es: **`channel` no se puede elegir**. «You cannot override the
|
|
68
|
+
* default channel (chosen by the user who installed your app)». El destino se
|
|
69
|
+
* decide al crear la credencial, así que ofrecer el campo sería ofrecer una
|
|
70
|
+
* pregunta cuya respuesta se ignora — que es peor que no ofrecerla.
|
|
71
|
+
*
|
|
72
|
+
* `text` y `blocks` se quedan (Block Kit está soportado), y `threadTs` también:
|
|
73
|
+
* la doc lo admite si ya tienes el `ts` del padre por otra vía.
|
|
74
|
+
*/
|
|
75
|
+
export declare const SLACK_CAPACIDADES_POR_CREDENCIAL: CapacidadesPorCredencial<SlackOperation>;
|
|
76
|
+
/** Las operaciones que ofrecer para una credencial de Slack. */
|
|
77
|
+
export declare function operacionesDeSlackPara(tipoDeCredencial: string | null | undefined): readonly SlackOperation[];
|
|
78
|
+
/** Si esa credencial puede ejecutar esa operación de Slack. */
|
|
79
|
+
export declare function slackPuedeEjecutar(tipoDeCredencial: string | null | undefined, operacion: SlackOperation): boolean;
|
|
80
|
+
/** Los campos que esa credencial de Slack no deja elegir. */
|
|
81
|
+
export declare function camposDeSlackNoDisponibles(tipoDeCredencial: string | null | undefined): ReadonlySet<string>;
|
package/dist/slack-operations.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = void 0;
|
|
3
|
+
exports.SLACK_CAPACIDADES_POR_CREDENCIAL = exports.SLACK_OPERATION_SPECS = exports.SLACK_OPERATIONS = void 0;
|
|
4
4
|
exports.isSlackOperation = isSlackOperation;
|
|
5
|
+
exports.operacionesDeSlackPara = operacionesDeSlackPara;
|
|
6
|
+
exports.slackPuedeEjecutar = slackPuedeEjecutar;
|
|
7
|
+
exports.camposDeSlackNoDisponibles = camposDeSlackNoDisponibles;
|
|
5
8
|
/**
|
|
6
9
|
* Slack Web API operation enum — single source of truth across the
|
|
7
10
|
* api, dashboard, and downstream consumers (Message Broker, MCP server).
|
|
@@ -15,6 +18,7 @@ exports.isSlackOperation = isSlackOperation;
|
|
|
15
18
|
* publish, scheduled.list, etc.) extend this list — bump the package and
|
|
16
19
|
* re-install in consumers, same pattern as Gmail / Calendar / Discord.
|
|
17
20
|
*/
|
|
21
|
+
const capacidad_de_credencial_1 = require("./capacidad-de-credencial");
|
|
18
22
|
exports.SLACK_OPERATIONS = [
|
|
19
23
|
// Messages
|
|
20
24
|
'sendMessage',
|
|
@@ -207,3 +211,41 @@ exports.SLACK_OPERATION_SPECS = {
|
|
|
207
211
|
],
|
|
208
212
|
},
|
|
209
213
|
};
|
|
214
|
+
/**
|
|
215
|
+
* Lo que puede hacer una credencial de Slack que NO es OAuth.
|
|
216
|
+
*
|
|
217
|
+
* Medido contra la documentación de Slack, no de memoria:
|
|
218
|
+
* https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks
|
|
219
|
+
*
|
|
220
|
+
* Es el caso extremo, y conviene que se lea así de crudo: **una** operación de
|
|
221
|
+
* las 16. Un incoming webhook publica y nada más. Textualmente: «Incoming
|
|
222
|
+
* webhooks do not allow you to delete a message after it's been posted», no
|
|
223
|
+
* lee, no sube ficheros, y editar exige `chat.update`, que es token.
|
|
224
|
+
*
|
|
225
|
+
* Y el campo que se cae es el que más duele, porque no parece una restricción
|
|
226
|
+
* hasta que lo es: **`channel` no se puede elegir**. «You cannot override the
|
|
227
|
+
* default channel (chosen by the user who installed your app)». El destino se
|
|
228
|
+
* decide al crear la credencial, así que ofrecer el campo sería ofrecer una
|
|
229
|
+
* pregunta cuya respuesta se ignora — que es peor que no ofrecerla.
|
|
230
|
+
*
|
|
231
|
+
* `text` y `blocks` se quedan (Block Kit está soportado), y `threadTs` también:
|
|
232
|
+
* la doc lo admite si ya tienes el `ts` del padre por otra vía.
|
|
233
|
+
*/
|
|
234
|
+
exports.SLACK_CAPACIDADES_POR_CREDENCIAL = {
|
|
235
|
+
slack_webhook: {
|
|
236
|
+
operaciones: ['sendMessage'],
|
|
237
|
+
camposNoDisponibles: ['channel'],
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
/** Las operaciones que ofrecer para una credencial de Slack. */
|
|
241
|
+
function operacionesDeSlackPara(tipoDeCredencial) {
|
|
242
|
+
return (0, capacidad_de_credencial_1.operacionesPara)(exports.SLACK_OPERATIONS, exports.SLACK_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial);
|
|
243
|
+
}
|
|
244
|
+
/** Si esa credencial puede ejecutar esa operación de Slack. */
|
|
245
|
+
function slackPuedeEjecutar(tipoDeCredencial, operacion) {
|
|
246
|
+
return (0, capacidad_de_credencial_1.puedeEjecutar)(exports.SLACK_OPERATIONS, exports.SLACK_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial, operacion);
|
|
247
|
+
}
|
|
248
|
+
/** Los campos que esa credencial de Slack no deja elegir. */
|
|
249
|
+
function camposDeSlackNoDisponibles(tipoDeCredencial) {
|
|
250
|
+
return (0, capacidad_de_credencial_1.camposNoDisponiblesPara)(exports.SLACK_CAPACIDADES_POR_CREDENCIAL, tipoDeCredencial);
|
|
251
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack AI Toolkit — las herramientas que un slackAction expone cuando
|
|
3
|
+
* `aiEnabled` está encendido.
|
|
4
|
+
*
|
|
5
|
+
* **Se escribe una sola vez.** Antes vivía dos veces: una copia en
|
|
6
|
+
* `dashboard/components/slack-actions/slack-operations-schemas.ts` y otra en
|
|
7
|
+
* `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y en repos distintos,
|
|
8
|
+
* así que ningún test podía compararlas. Es la octava y novena mudanza de esta
|
|
9
|
+
* serie; de las siete anteriores, la de Contacts salió de que una copia
|
|
10
|
+
* anunciara 15 herramientas y expusiera una.
|
|
11
|
+
*
|
|
12
|
+
* La deriva aquí era gorda: **14 de las 16 descripciones** diferían. Gana la de
|
|
13
|
+
* la api en todas —es sistemáticamente la más completa y es la que sirve el MCP
|
|
14
|
+
* hoy—, y una no era cosmética: en `deleteMessage` la api nombra el permiso que
|
|
15
|
+
* hace falta (`chat:write.public` + admin) y el dashboard sólo decía «admin
|
|
16
|
+
* perms». Operaciones, nombres de herramienta y parámetros sí coincidían.
|
|
17
|
+
*
|
|
18
|
+
* Los nombres llevan `_slack_` para no chocar con los de Telegram y Discord
|
|
19
|
+
* cuando un mismo AI Node tiene varios toolkits encendidos.
|
|
20
|
+
*
|
|
21
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
|
|
22
|
+
* pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
|
|
23
|
+
* y quien mire la lista de herramientas del servidor MCP.
|
|
24
|
+
*/
|
|
25
|
+
import type { SlackOperation } from './slack-operations';
|
|
26
|
+
export interface SlackToolkitParameter {
|
|
27
|
+
name: string;
|
|
28
|
+
type: 'string' | 'number' | 'boolean';
|
|
29
|
+
description: string;
|
|
30
|
+
required: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface SlackToolkitSpec {
|
|
33
|
+
operation: SlackOperation;
|
|
34
|
+
/** Etiqueta corta de la fila en la lista de herramientas. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** Nombre con el que el LLM llama a la herramienta. */
|
|
37
|
+
toolName: string;
|
|
38
|
+
/** Descripción (más reglas de uso) que ve el LLM. */
|
|
39
|
+
description: string;
|
|
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. */
|
|
44
|
+
destructive?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare const SLACK_TOOLKIT_SPECS: SlackToolkitSpec[];
|
|
47
|
+
export declare const SLACK_TOOLKIT_BY_TOOL_NAME: Record<string, SlackToolkitSpec>;
|
|
48
|
+
/**
|
|
49
|
+
* Las herramientas que ofrecerle al modelo con ESTA credencial.
|
|
50
|
+
*
|
|
51
|
+
* Gemelo de `herramientasDeDiscordPara`, y por la misma razón: los dos
|
|
52
|
+
* consumidores —la expansión MCP de la api y el selector del AI Node del
|
|
53
|
+
* dashboard— filtrarían por su cuenta y acabarían discrepando.
|
|
54
|
+
*
|
|
55
|
+
* En Slack el recorte es brutal y conviene verlo escrito: con un webhook el
|
|
56
|
+
* modelo pasa de **16 herramientas a una**, y esa una pierde el `channel`,
|
|
57
|
+
* porque el destino lo fijó quien instaló la credencial. Dejarle el campo sería
|
|
58
|
+
* invitarle a elegir un canal que Slack va a ignorar sin decir nada.
|
|
59
|
+
*/
|
|
60
|
+
export declare function herramientasDeSlackPara(tipoDeCredencial: string | null | undefined): SlackToolkitSpec[];
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Slack AI Toolkit — las herramientas que un slackAction expone cuando
|
|
4
|
+
* `aiEnabled` está encendido.
|
|
5
|
+
*
|
|
6
|
+
* **Se escribe una sola vez.** Antes vivía dos veces: una copia en
|
|
7
|
+
* `dashboard/components/slack-actions/slack-operations-schemas.ts` y otra en
|
|
8
|
+
* `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y en repos distintos,
|
|
9
|
+
* así que ningún test podía compararlas. Es la octava y novena mudanza de esta
|
|
10
|
+
* serie; de las siete anteriores, la de Contacts salió de que una copia
|
|
11
|
+
* anunciara 15 herramientas y expusiera una.
|
|
12
|
+
*
|
|
13
|
+
* La deriva aquí era gorda: **14 de las 16 descripciones** diferían. Gana la de
|
|
14
|
+
* la api en todas —es sistemáticamente la más completa y es la que sirve el MCP
|
|
15
|
+
* hoy—, y una no era cosmética: en `deleteMessage` la api nombra el permiso que
|
|
16
|
+
* hace falta (`chat:write.public` + admin) y el dashboard sólo decía «admin
|
|
17
|
+
* perms». Operaciones, nombres de herramienta y parámetros sí coincidían.
|
|
18
|
+
*
|
|
19
|
+
* Los nombres llevan `_slack_` para no chocar con los de Telegram y Discord
|
|
20
|
+
* cuando un mismo AI Node tiene varios toolkits encendidos.
|
|
21
|
+
*
|
|
22
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
|
|
23
|
+
* pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
|
|
24
|
+
* y quien mire la lista de herramientas del servidor MCP.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.SLACK_TOOLKIT_BY_TOOL_NAME = exports.SLACK_TOOLKIT_SPECS = void 0;
|
|
28
|
+
exports.herramientasDeSlackPara = herramientasDeSlackPara;
|
|
29
|
+
const slack_operations_1 = require("./slack-operations");
|
|
30
|
+
const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
|
|
31
|
+
exports.SLACK_TOOLKIT_SPECS = [
|
|
32
|
+
// ── Messages ─────────────────────────────────────────────────────
|
|
33
|
+
{
|
|
34
|
+
operation: 'sendMessage',
|
|
35
|
+
label: 'Send message',
|
|
36
|
+
toolName: 'send_slack_message',
|
|
37
|
+
description: 'Post a message to a Slack channel, group, or DM. ' +
|
|
38
|
+
'USAGE RULES: ' +
|
|
39
|
+
'(1) `channel` is a Slack channel id (C…), group id (G…), or DM id (D…). Get it from the trigger payload, listChannels, or the user. Never invent one. ' +
|
|
40
|
+
'(2) For a thread reply, set `threadTs` to the parent message timestamp. ' +
|
|
41
|
+
'(3) Either `text` or `blocks` is required. Use blocks for rich layouts.',
|
|
42
|
+
parameters: [
|
|
43
|
+
p('channel', 'Channel/group/DM id where the message will be posted.'),
|
|
44
|
+
p('text', 'Message text. Slack mrkdwn supported. Required when blocks is empty.', false),
|
|
45
|
+
p('blocks', 'Block Kit JSON for rich layouts. Stringified JSON or JSON array.', false),
|
|
46
|
+
p('threadTs', 'Parent message timestamp to reply inside an existing thread.', false),
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
operation: 'updateMessage',
|
|
51
|
+
label: 'Update message',
|
|
52
|
+
toolName: 'update_slack_message',
|
|
53
|
+
description: 'Edit a message previously posted by the bot. The bot can only edit its own messages — editing another user/bot message returns cant_update_message.',
|
|
54
|
+
parameters: [
|
|
55
|
+
p('channel', 'Channel where the original message lives.'),
|
|
56
|
+
p('ts', 'Timestamp of the message to edit (returned by sendMessage).'),
|
|
57
|
+
p('text', 'New message text. Required when blocks is empty.', false),
|
|
58
|
+
p('blocks', 'New Block Kit JSON. Replaces the existing layout.', false),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
operation: 'deleteMessage',
|
|
63
|
+
label: 'Delete message',
|
|
64
|
+
toolName: 'delete_slack_message',
|
|
65
|
+
description: "Delete a message the bot posted. Other users' messages need chat:write.public + admin perms — usually fails for AI-driven calls. Confirm before destructive use.",
|
|
66
|
+
parameters: [
|
|
67
|
+
p('channel', 'Channel where the message lives.'),
|
|
68
|
+
p('ts', 'Timestamp of the message to delete.'),
|
|
69
|
+
],
|
|
70
|
+
destructive: true,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
operation: 'sendEphemeral',
|
|
74
|
+
label: 'Send ephemeral',
|
|
75
|
+
toolName: 'send_slack_ephemeral',
|
|
76
|
+
description: 'Post a message visible only to a specific user — the rest of the channel does not see it. Useful for per-user prompts, errors, or warnings.',
|
|
77
|
+
parameters: [
|
|
78
|
+
p('channel', 'Channel where the ephemeral message will appear.'),
|
|
79
|
+
p('user', 'User id (U…) — the only viewer of the message.'),
|
|
80
|
+
p('text', 'Message text. Required when blocks is empty.', false),
|
|
81
|
+
p('blocks', 'Block Kit JSON for rich layouts.', false),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
operation: 'scheduleMessage',
|
|
86
|
+
label: 'Schedule message',
|
|
87
|
+
toolName: 'schedule_slack_message',
|
|
88
|
+
description: 'Schedule a Slack message to be posted at a future time. Returns a scheduled_message_id useful for cancellation later.',
|
|
89
|
+
parameters: [
|
|
90
|
+
p('channel', 'Channel where the message will be posted.'),
|
|
91
|
+
p('postAt', 'When to post — unix timestamp in SECONDS, not milliseconds.', true, 'number'),
|
|
92
|
+
p('text', 'Message text. Required when blocks is empty.', false),
|
|
93
|
+
p('blocks', 'Block Kit JSON for rich layouts.', false),
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
operation: 'getMessage',
|
|
98
|
+
label: 'Get message',
|
|
99
|
+
toolName: 'get_slack_message',
|
|
100
|
+
description: 'Fetch a single message by its timestamp. Returns the message text + author + reactions + attachments. Use BEFORE editing or reacting to verify state.',
|
|
101
|
+
parameters: [
|
|
102
|
+
p('channel', 'Channel where the message lives.'),
|
|
103
|
+
p('ts', 'Timestamp of the message to fetch.'),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
// ── Channels ─────────────────────────────────────────────────────
|
|
107
|
+
{
|
|
108
|
+
operation: 'listChannels',
|
|
109
|
+
label: 'List channels',
|
|
110
|
+
toolName: 'list_slack_channels',
|
|
111
|
+
description: 'List public + private channels the bot can see in the workspace. Returns id, name, isMember, memberCount per channel. Pre-pick a channel before sendMessage.',
|
|
112
|
+
parameters: [
|
|
113
|
+
p('limit', 'Max channels to return (1-1000). Default 200.', false, 'number'),
|
|
114
|
+
p('excludeArchived', 'Hide archived channels. Default true.', false, 'boolean'),
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
operation: 'getChannelHistory',
|
|
119
|
+
label: 'Get channel history',
|
|
120
|
+
toolName: 'get_slack_channel_history',
|
|
121
|
+
description: 'Read recent messages from a Slack channel. Useful for summarization, context retrieval, or finding a message by content before editing/reacting.',
|
|
122
|
+
parameters: [
|
|
123
|
+
p('channel', 'Channel id to read from.'),
|
|
124
|
+
p('limit', 'Max messages to return (1-200). Default 50.', false, 'number'),
|
|
125
|
+
p('oldest', 'Only messages newer than this timestamp.', false),
|
|
126
|
+
p('latest', 'Only messages older than this timestamp.', false),
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
operation: 'getThreadReplies',
|
|
131
|
+
label: 'Get thread replies',
|
|
132
|
+
toolName: 'get_slack_thread_replies',
|
|
133
|
+
description: 'Fetch the replies inside a Slack thread, given the parent message timestamp. Use for summarizing or continuing a thread.',
|
|
134
|
+
parameters: [
|
|
135
|
+
p('channel', 'Channel where the thread lives.'),
|
|
136
|
+
p('ts', 'Timestamp of the thread parent message.'),
|
|
137
|
+
p('limit', 'Max replies to return (1-1000). Default 100.', false, 'number'),
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
operation: 'createChannel',
|
|
142
|
+
label: 'Create channel',
|
|
143
|
+
toolName: 'create_slack_channel',
|
|
144
|
+
description: 'Create a new public or private Slack channel. Names must be lowercase, no spaces, max 80 chars. Confirm with the user before creating channels — they are usually long-lived org artifacts.',
|
|
145
|
+
parameters: [
|
|
146
|
+
p('name', 'Channel name — lowercase, no spaces, max 80 chars.'),
|
|
147
|
+
p('isPrivate', 'Create a private channel instead of public. Default false.', false, 'boolean'),
|
|
148
|
+
],
|
|
149
|
+
destructive: true,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
operation: 'inviteToChannel',
|
|
153
|
+
label: 'Invite to channel',
|
|
154
|
+
toolName: 'invite_to_slack_channel',
|
|
155
|
+
description: 'Invite one or more users to a Slack channel by user id. Comma-separated `users` argument.',
|
|
156
|
+
parameters: [
|
|
157
|
+
p('channel', 'Channel id to invite users to.'),
|
|
158
|
+
p('users', 'Comma-separated user ids (U…) to invite.'),
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
// ── Reactions ────────────────────────────────────────────────────
|
|
162
|
+
{
|
|
163
|
+
operation: 'addReaction',
|
|
164
|
+
label: 'Add reaction',
|
|
165
|
+
toolName: 'add_slack_reaction',
|
|
166
|
+
description: 'Add an emoji reaction to a Slack message as the bot. Use the emoji NAME without colons (e.g. `thumbsup`, `white_check_mark`, `eyes`).',
|
|
167
|
+
parameters: [
|
|
168
|
+
p('channel', 'Channel where the message lives.'),
|
|
169
|
+
p('ts', 'Timestamp of the message to react to.'),
|
|
170
|
+
p('name', 'Emoji name without colons.'),
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
operation: 'removeReaction',
|
|
175
|
+
label: 'Remove reaction',
|
|
176
|
+
toolName: 'remove_slack_reaction',
|
|
177
|
+
description: "Remove the bot's own emoji reaction from a Slack message. Cannot remove other users' reactions.",
|
|
178
|
+
parameters: [
|
|
179
|
+
p('channel', 'Channel where the message lives.'),
|
|
180
|
+
p('ts', 'Timestamp of the message.'),
|
|
181
|
+
p('name', 'Emoji name without colons.'),
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
// ── Users ────────────────────────────────────────────────────────
|
|
185
|
+
{
|
|
186
|
+
operation: 'listUsers',
|
|
187
|
+
label: 'List users',
|
|
188
|
+
toolName: 'list_slack_users',
|
|
189
|
+
description: 'List human members of the Slack workspace. Returns id, name, real_name, email, avatarUrl. Excludes deleted users + bots by default.',
|
|
190
|
+
parameters: [
|
|
191
|
+
p('limit', 'Max users to return (1-1000). Default 200.', false, 'number'),
|
|
192
|
+
p('includeDeleted', 'Include deactivated users. Default false.', false, 'boolean'),
|
|
193
|
+
p('includeBots', 'Include bot users + app users. Default false.', false, 'boolean'),
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
operation: 'getUserInfo',
|
|
198
|
+
label: 'Get user info',
|
|
199
|
+
toolName: 'get_slack_user_info',
|
|
200
|
+
description: 'Fetch a Slack user profile (real_name, email, timezone, status, avatar). Use BEFORE sendEphemeral to confirm the user id is valid.',
|
|
201
|
+
parameters: [p('user', 'User id (U…) to look up.')],
|
|
202
|
+
},
|
|
203
|
+
// ── Files ────────────────────────────────────────────────────────
|
|
204
|
+
{
|
|
205
|
+
operation: 'uploadFile',
|
|
206
|
+
label: 'Upload file',
|
|
207
|
+
toolName: 'upload_slack_file',
|
|
208
|
+
description: 'Upload a file to Slack and optionally share it in a channel. Provide either a public `url` to fetch or inline `contentBase64` for small payloads. Optionally attach to a thread via `threadTs`.',
|
|
209
|
+
parameters: [
|
|
210
|
+
p('url', 'Public URL or HW _file.downloadUrl that HW will fetch and re-upload.', false),
|
|
211
|
+
p('contentBase64', 'Inline base64 file content (alternative to url).', false),
|
|
212
|
+
p('filename', 'Display name in Slack. Auto-derived from URL when omitted.', false),
|
|
213
|
+
p('channel', 'Channel id to share the uploaded file in.', false),
|
|
214
|
+
p('title', 'Title shown above the file.', false),
|
|
215
|
+
p('initialComment', 'Message Slack posts together with the file.', false),
|
|
216
|
+
p('threadTs', 'Thread to attach the file to.', false),
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
exports.SLACK_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(exports.SLACK_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
|
|
221
|
+
/**
|
|
222
|
+
* Las herramientas que ofrecerle al modelo con ESTA credencial.
|
|
223
|
+
*
|
|
224
|
+
* Gemelo de `herramientasDeDiscordPara`, y por la misma razón: los dos
|
|
225
|
+
* consumidores —la expansión MCP de la api y el selector del AI Node del
|
|
226
|
+
* dashboard— filtrarían por su cuenta y acabarían discrepando.
|
|
227
|
+
*
|
|
228
|
+
* En Slack el recorte es brutal y conviene verlo escrito: con un webhook el
|
|
229
|
+
* modelo pasa de **16 herramientas a una**, y esa una pierde el `channel`,
|
|
230
|
+
* porque el destino lo fijó quien instaló la credencial. Dejarle el campo sería
|
|
231
|
+
* invitarle a elegir un canal que Slack va a ignorar sin decir nada.
|
|
232
|
+
*/
|
|
233
|
+
function herramientasDeSlackPara(tipoDeCredencial) {
|
|
234
|
+
const permitidas = new Set((0, slack_operations_1.operacionesDeSlackPara)(tipoDeCredencial));
|
|
235
|
+
const fuera = (0, slack_operations_1.camposDeSlackNoDisponibles)(tipoDeCredencial);
|
|
236
|
+
return exports.SLACK_TOOLKIT_SPECS.filter((s) => permitidas.has(s.operation)).map((s) => fuera.size === 0
|
|
237
|
+
? s
|
|
238
|
+
: { ...s, parameters: s.parameters.filter((p) => !fuera.has(p.name)) });
|
|
239
|
+
}
|
package/package.json
CHANGED