@hostwebhook/platform-contracts 0.2.0 → 0.3.1

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 (63) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/servicios/almacenes-vectoriales.d.ts +105 -0
  4. package/dist/servicios/almacenes-vectoriales.js +26 -0
  5. package/dist/servicios/aprobaciones-pendientes.d.ts +207 -0
  6. package/dist/servicios/aprobaciones-pendientes.js +51 -0
  7. package/dist/servicios/avisos-en-vivo.d.ts +106 -0
  8. package/dist/servicios/avisos-en-vivo.js +60 -0
  9. package/dist/servicios/canal-de-stream.d.ts +78 -0
  10. package/dist/servicios/canal-de-stream.js +5 -0
  11. package/dist/servicios/conversaciones-del-chat.d.ts +139 -0
  12. package/dist/servicios/conversaciones-del-chat.js +51 -0
  13. package/dist/servicios/corridas-programadas.d.ts +29 -0
  14. package/dist/servicios/corridas-programadas.js +5 -0
  15. package/dist/servicios/credencial/permiso-sobre-credencial.d.ts +61 -0
  16. package/dist/servicios/credencial/permiso-sobre-credencial.js +42 -0
  17. package/dist/servicios/credenciales-del-gateway.d.ts +722 -0
  18. package/dist/servicios/credenciales-del-gateway.js +114 -0
  19. package/dist/servicios/cuentas-de-atlassian.d.ts +22 -0
  20. package/dist/servicios/cuentas-de-atlassian.js +5 -0
  21. package/dist/servicios/descarga-de-drive.d.ts +74 -0
  22. package/dist/servicios/descarga-de-drive.js +41 -0
  23. package/dist/servicios/ejecucion-de-ia.d.ts +81 -0
  24. package/dist/servicios/ejecucion-de-ia.js +39 -0
  25. package/dist/servicios/enlaces-de-flujo.d.ts +59 -0
  26. package/dist/servicios/enlaces-de-flujo.js +27 -0
  27. package/dist/servicios/ficheros-del-gateway.d.ts +210 -0
  28. package/dist/servicios/ficheros-del-gateway.js +8 -0
  29. package/dist/servicios/formas-compartidas.d.ts +57 -0
  30. package/dist/servicios/formas-compartidas.js +22 -0
  31. package/dist/servicios/historial-de-corridas.d.ts +75 -0
  32. package/dist/servicios/historial-de-corridas.js +7 -0
  33. package/dist/servicios/index.d.ts +57 -0
  34. package/dist/servicios/index.js +44 -0
  35. package/dist/servicios/limites-del-plan.d.ts +82 -0
  36. package/dist/servicios/limites-del-plan.js +41 -0
  37. package/dist/servicios/motor-de-ejecucion.d.ts +197 -0
  38. package/dist/servicios/motor-de-ejecucion.js +11 -0
  39. package/dist/servicios/plantillas-de-origen.d.ts +25 -0
  40. package/dist/servicios/plantillas-de-origen.js +5 -0
  41. package/dist/servicios/posts-sociales.d.ts +69 -0
  42. package/dist/servicios/posts-sociales.js +23 -0
  43. package/dist/servicios/registro-de-entregas.d.ts +93 -0
  44. package/dist/servicios/registro-de-entregas.js +46 -0
  45. package/dist/servicios/registro-de-eventos.d.ts +135 -0
  46. package/dist/servicios/registro-de-eventos.js +62 -0
  47. package/dist/servicios/restauracion-de-nodos.d.ts +53 -0
  48. package/dist/servicios/restauracion-de-nodos.js +4 -0
  49. package/dist/servicios/salud-del-webhook.d.ts +26 -0
  50. package/dist/servicios/salud-del-webhook.js +6 -0
  51. package/dist/servicios/secretos-de-firma.d.ts +26 -0
  52. package/dist/servicios/secretos-de-firma.js +5 -0
  53. package/dist/servicios/telemetria.d.ts +114 -0
  54. package/dist/servicios/telemetria.js +60 -0
  55. package/dist/servicios/trazas-de-llm.d.ts +79 -0
  56. package/dist/servicios/trazas-de-llm.js +23 -0
  57. package/dist/servicios/tuneles.d.ts +106 -0
  58. package/dist/servicios/tuneles.js +87 -0
  59. package/dist/servicios/workspace/acceso-al-recurso.d.ts +39 -0
  60. package/dist/servicios/workspace/acceso-al-recurso.js +5 -0
  61. package/dist/servicios/zona-horaria-del-usuario.d.ts +40 -0
  62. package/dist/servicios/zona-horaria-del-usuario.js +7 -0
  63. package/package.json +9 -3
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@
11
11
  */
12
12
  export * from './addons';
13
13
  export * from './operadores';
14
+ export * from './servicios';
package/dist/index.js CHANGED
@@ -27,3 +27,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
27
27
  */
28
28
  __exportStar(require("./addons"), exports);
29
29
  __exportStar(require("./operadores"), exports);
30
+ __exportStar(require("./servicios"), exports);
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Lo que un nodo le pide a un almacén vectorial.
3
+ *
4
+ * Costura de la Fase 2, del lote de las pequeñas. `VectorStoresService`
5
+ * arrastra tres colecciones, los backends de vectores —Atlas, pgvector,
6
+ * Pinecone—, el cifrado de credenciales y el registro de accesos. El nodo
7
+ * sólo consulta, inserta y borra.
8
+ *
9
+ * ## ⚠️ Lo que NO puede perderse: la marca de quién
10
+ *
11
+ * Los tres métodos que TOCAN datos llevan `userId`, `sourceIp` y
12
+ * `relatedEntity`, y no son telemetría de adorno: son lo que el registro de
13
+ * accesos del almacén guarda para poder decir quién consultó qué. Hay un
14
+ * guardián dedicado a eso (`el-nodo-se-firma-en-el-registro.spec.ts`) porque
15
+ * un nodo que se cuela sin firmar deja el registro mintiendo.
16
+ *
17
+ * ⚠️ Y `relatedEntity` es lo que distingue a un nodo de su DUEÑO: un nodo
18
+ * corre con el `userId` del usuario que lo creó, así que sin la entidad
19
+ * relacionada la fila del registro dice «lo consultó Ariel» cuando lo
20
+ * consultó un flujo. Ver `project_el_registro_dice_quien`.
21
+ */
22
+ /** Quién —o qué— está detrás de una operación sobre el almacén. */
23
+ export interface EntidadRelacionada {
24
+ type: string;
25
+ id: string;
26
+ name?: string;
27
+ }
28
+ /** Un trozo devuelto por una consulta. */
29
+ export interface TrozoEncontrado {
30
+ chunkId: string;
31
+ content: string;
32
+ score: number;
33
+ sourceDocId?: string;
34
+ sourceDocName?: string;
35
+ sourceDocType?: string;
36
+ metadata?: Record<string, unknown>;
37
+ createdAt?: Date;
38
+ }
39
+ import type { ChunkingStrategy } from './formas-compartidas';
40
+ /**
41
+ * El troceo que el almacén trae de fábrica.
42
+ *
43
+ * ⚠️ `chunkSize` y `chunkOverlap` son en CARACTERES, no en tokens. Está en el
44
+ * contrato porque es justo lo que se malinterpreta al leerlo de fuera; ver
45
+ * `project_vector_store_troceo`.
46
+ */
47
+ export interface TroceoPorDefecto {
48
+ strategy: ChunkingStrategy;
49
+ chunkSize: number;
50
+ chunkOverlap: number;
51
+ separators?: string[] | null;
52
+ }
53
+ export interface ConsultaAlAlmacen {
54
+ query: string;
55
+ topK?: number;
56
+ filter?: Record<string, unknown>;
57
+ minScore?: number;
58
+ }
59
+ export interface BorradoDeTrozos {
60
+ chunkIds?: string[];
61
+ sourceDocId?: string;
62
+ metadata?: Record<string, unknown>;
63
+ }
64
+ export interface AlmacenesVectoriales {
65
+ query(storeId: string, orgId: string, dto: ConsultaAlAlmacen, userId: string, sourceIp: string, relatedEntity?: EntidadRelacionada): Promise<{
66
+ matches: TrozoEncontrado[];
67
+ tookMs: number;
68
+ storeName: string;
69
+ storeId: string;
70
+ }>;
71
+ /**
72
+ * El almacén, para leerle la configuración de troceo por defecto.
73
+ *
74
+ * ⚠️ Devuelve SÓLO `chunking`, que es lo único que su llamante lee — se
75
+ * comprobó campo por campo. El documento entero trae además el backend, sus
76
+ * credenciales cifradas y el recuento de vectores; nada de eso tiene por
77
+ * qué cruzar.
78
+ *
79
+ * ⚠️ Y lanza si el almacén no es de esa organización. Eso hay que
80
+ * conservarlo: es la comprobación de propiedad, no un detalle.
81
+ */
82
+ getStore(id: string, orgId: string): Promise<{
83
+ chunking: TroceoPorDefecto;
84
+ }>;
85
+ findExistingHashes(storeId: string, orgId: string, hashes: string[]): Promise<string[]>;
86
+ deleteChunks(storeId: string, orgId: string, dto: BorradoDeTrozos, userId: string, sourceIp: string, relatedEntity?: EntidadRelacionada): Promise<{
87
+ deletedCount: number;
88
+ }>;
89
+ ingestPreChunked(storeId: string, orgId: string, opts: {
90
+ chunks: Array<{
91
+ content: string;
92
+ metadata?: Record<string, unknown>;
93
+ }>;
94
+ sourceDocId?: string;
95
+ sourceDocName?: string;
96
+ sourceDocType?: string;
97
+ baseMetadata?: Record<string, unknown>;
98
+ }, userId: string, sourceIp: string, relatedEntity?: EntidadRelacionada): Promise<{
99
+ chunksInserted: number;
100
+ skipped: number;
101
+ bytes: number;
102
+ }>;
103
+ }
104
+ /** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
105
+ export declare const ALMACENES_VECTORIALES = "ALMACENES_VECTORIALES";
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * Lo que un nodo le pide a un almacén vectorial.
4
+ *
5
+ * Costura de la Fase 2, del lote de las pequeñas. `VectorStoresService`
6
+ * arrastra tres colecciones, los backends de vectores —Atlas, pgvector,
7
+ * Pinecone—, el cifrado de credenciales y el registro de accesos. El nodo
8
+ * sólo consulta, inserta y borra.
9
+ *
10
+ * ## ⚠️ Lo que NO puede perderse: la marca de quién
11
+ *
12
+ * Los tres métodos que TOCAN datos llevan `userId`, `sourceIp` y
13
+ * `relatedEntity`, y no son telemetría de adorno: son lo que el registro de
14
+ * accesos del almacén guarda para poder decir quién consultó qué. Hay un
15
+ * guardián dedicado a eso (`el-nodo-se-firma-en-el-registro.spec.ts`) porque
16
+ * un nodo que se cuela sin firmar deja el registro mintiendo.
17
+ *
18
+ * ⚠️ Y `relatedEntity` es lo que distingue a un nodo de su DUEÑO: un nodo
19
+ * corre con el `userId` del usuario que lo creó, así que sin la entidad
20
+ * relacionada la fila del registro dice «lo consultó Ariel» cuando lo
21
+ * consultó un flujo. Ver `project_el_registro_dice_quien`.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.ALMACENES_VECTORIALES = void 0;
25
+ /** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
26
+ exports.ALMACENES_VECTORIALES = 'ALMACENES_VECTORIALES';
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Lo que una PUERTA del gateway necesita de las aprobaciones pendientes.
3
+ *
4
+ * ## Por qué existe
5
+ *
6
+ * `source-nodes/triggers` —el trigger de Telegram— y `social-posts` viven en el
7
+ * gateway; `approval-nodes` se muda a hw-nodes. Entre los dos había cuatro
8
+ * llamadas directas a `ApprovalNodesService`, y una de ellas pasaba un
9
+ * **documento de Mongoose vivo** al motor.
10
+ *
11
+ * ## 🔥 Por qué el contrato NO es «los mismos cuatro métodos»
12
+ *
13
+ * El código de hoy hace esto:
14
+ *
15
+ * const fila = await pendingService.resolvePendingByToken({…});
16
+ * await eventPipeline.resumeAfterSendAndWait(fila, {…});
17
+ *
18
+ * O sea: la puerta resuelve la fila y le pasa el DOCUMENTO al motor, que le lee
19
+ * seis campos más (`eventId`, `organizationId`, `webhookId`, `payload`,
20
+ * `eventHeaders`, `id`). Un documento de Mongoose no cruza una red, así que
21
+ * copiar esos métodos tal cual obligaría a serializar la fila entera y a
22
+ * mantener las dos formas en paralelo.
23
+ *
24
+ * Pero es que no hace falta: **el motor y las aprobaciones están del MISMO
25
+ * lado**. Los dos se van a hw-nodes. Lo único que cruza es la decisión —quién
26
+ * pulsó, cuándo, qué escribió—, y el documento se queda donde vive.
27
+ *
28
+ * Por eso hay un solo método para las dos cosas, `resolverYReanudar`.
29
+ *
30
+ * ## ⚠️ La distinción que hay que conservar
31
+ *
32
+ * Hoy los dos fallos NO se tratan igual, y eso es visible para el usuario:
33
+ *
34
+ * - si no se puede RESOLVER la fila, a Telegram le vuelve «No pude registrar
35
+ * tu respuesta. Inténtalo de nuevo»;
36
+ * - si falla el REANUDADO, se registra y ya: la fila está resuelta, el
37
+ * usuario ve su confirmación, y volver a lanzar aguas abajo necesitaría a
38
+ * un operador de todas formas.
39
+ *
40
+ * Por eso `resolverYReanudar` devuelve un booleano que habla SÓLO de la
41
+ * resolución. Fundir los dos fallos en uno cambiaría lo que ve el usuario sin
42
+ * tocar una línea de la UI.
43
+ */
44
+ /** La fila pendiente, en lo que la puerta lee de ella. Seis campos, medidos. */
45
+ export interface AprobacionPendiente {
46
+ /** El `id` virtual de Mongoose, ya en cadena. */
47
+ id: string;
48
+ /** `pending` | `approved` | `rejected` | `expired`. */
49
+ status: string;
50
+ /** De dónde salió: `telegramSendAndWait`, `approval`, `socialMedia`… */
51
+ source: string;
52
+ /** El contexto con el que se creó. La puerta lo lee entero y opaco. */
53
+ pipelineContext: Record<string, unknown>;
54
+ /** El nodo que la pidió, o `null` si no hubo uno. */
55
+ sourceNodeId: string | null;
56
+ /** El token con el que se resuelve. */
57
+ resolveToken: string;
58
+ /** De quién es. Las puertas públicas lo necesitan para acotar sus lecturas. */
59
+ organizationId: string;
60
+ /** El evento que la creó, si lo hubo. */
61
+ eventId: string | null;
62
+ /** El webhook que lo trajo, si lo hubo. */
63
+ webhookId: string | null;
64
+ /** Cuándo se pidió y cuándo caduca — la página se lo dice a quien llega
65
+ * tarde. */
66
+ requestedAt: Date | null;
67
+ expiresAt: Date | null;
68
+ }
69
+ /** Quién decidió, cuándo y qué dijo. Es lo único que cruza. */
70
+ export interface DecisionSobrePendiente {
71
+ token: string;
72
+ aprobada: boolean;
73
+ decidedAt: Date;
74
+ /** Etiqueta legible: `telegram:ariel`, `telegram-callback`… */
75
+ decidedBy: string;
76
+ /** De dónde vino la decisión, para la auditoría. */
77
+ ipAddress: string;
78
+ comment?: string;
79
+ /**
80
+ * La respuesta escrita a mano, cuando la hay.
81
+ *
82
+ * ⚠️ Sólo el camino de texto libre de Telegram la trae; un botón no. Sale en
83
+ * `_waitResponse.responseText` para que un Conditional o un nodo de IA de
84
+ * abajo puedan leerla.
85
+ */
86
+ responseText?: string;
87
+ }
88
+ export interface AprobacionesPendientes {
89
+ /** La fila de ese token, o `null` si no existe. */
90
+ porToken(token: string): Promise<AprobacionPendiente | null>;
91
+ /**
92
+ * La pendiente MÁS ANTIGUA de ese chat de Telegram que admita texto libre.
93
+ *
94
+ * ⚠️ Primero en llegar, primero en resolverse: si un chat tiene dos
95
+ * esperando, la respuesta escrita resuelve la de arriba. Y la comparación va
96
+ * por tipo exacto —`telegramChatId` se guarda como NÚMERO y `allowFreeText`
97
+ * como BOOLEANO—, o una coerción de cadena a número deja de encontrar la
98
+ * fila sin decir nada.
99
+ */
100
+ deTelegram(orgId: string, chatId: number | string): Promise<AprobacionPendiente | null>;
101
+ /**
102
+ * Resuelve la fila y reanuda el flujo, del otro lado y en proceso.
103
+ *
104
+ * ⚠️ El booleano habla SÓLO de la resolución. Un fallo al reanudar se
105
+ * registra y devuelve `true`, porque la fila ya está resuelta y el usuario ya
106
+ * tiene su confirmación. Ver la cabecera.
107
+ */
108
+ resolverYReanudar(decision: DecisionSobrePendiente): Promise<boolean>;
109
+ /**
110
+ * Resuelve por ID y reanuda. Hermana de `resolverYReanudar`.
111
+ *
112
+ * ⚠️ Son DOS y no una porque abajo hay dos métodos distintos, no dos
113
+ * nombres del mismo: `resolvePendingByToken` empareja por token, y `resolve`
114
+ * empareja por id Y comprueba `allowedApproverIds` cuando hay un usuario
115
+ * detrás. Fundirlas aquí escondería esa comprobación —una aprobación
116
+ * restringida al director la resolvía cualquier empleado hasta que se
117
+ * arregló—, así que cada puerta llama a la suya.
118
+ *
119
+ * El enlace de un clic no trae identidad: resuelve con una etiqueta, así que
120
+ * la comprobación de aprobador no le aplica. Pero el método sí es el otro.
121
+ */
122
+ resolverPorIdYReanudar(decision: {
123
+ pendingId: string;
124
+ orgId: string;
125
+ aprobada: boolean;
126
+ decidedAt: Date;
127
+ /** Etiqueta, no identidad: en un enlace de un clic sólo hay una IP. */
128
+ decidedBy: string;
129
+ ipAddress?: string;
130
+ comment?: string;
131
+ }): Promise<boolean>;
132
+ /**
133
+ * Resuelve y NO encadena nada. Devuelve la fila resuelta, aplanada.
134
+ *
135
+ * ⚠️ Es la tercera resolución del contrato, y las tres existen porque lo que
136
+ * SIGUE a una resolución es distinto en cada puerta:
137
+ *
138
+ * `resolverYReanudar` resuelve por token → reanuda el flujo
139
+ * `resolverPorIdYReanudar` resuelve por id → reanuda el flujo
140
+ * `resolverAprobacionDeRedes` resuelve por token → nada, el que llama decide
141
+ *
142
+ * La de redes se bifurca DESPUÉS: un post del calendario se publica por su
143
+ * camino, que es del gateway, y el plan de un nodo por el suyo, que es del
144
+ * ejecutor. Fundir eso aquí obligaría al contrato a saber de calendarios.
145
+ *
146
+ * ⚠️ Devuelve la fila porque quien llama necesita su `pipelineContext` para
147
+ * elegir la rama — y `null` cuando no ganó la reclamación, que es lo que
148
+ * distingue «resuelta por mí» de «alguien se me adelantó».
149
+ */
150
+ resolverAprobacionDeRedes(decision: {
151
+ token: string;
152
+ aprobada: boolean;
153
+ decidedBy: string;
154
+ comment?: string | null;
155
+ }): Promise<AprobacionPendiente | null>;
156
+ /**
157
+ * Publica el plan de una fila que QUIEN LLAMA acaba de reclamar.
158
+ *
159
+ * ⚠️ El nombre lo dice a propósito: la garantía de una sola publicación no la
160
+ * da el estado de la fila, la da haber ganado la reclamación. Llamar a esto
161
+ * sin haberla ganado es un error, y del otro lado se rechaza.
162
+ */
163
+ publicarPlanReclamado(pendingId: string, orgId: string): Promise<{
164
+ statusCode: number;
165
+ responseBody: string;
166
+ }>;
167
+ /**
168
+ * ¿La página de rechazo tiene que pedir un comentario?
169
+ *
170
+ * ⚠️ Sale de `operationConfig.requireDisapproveComment` del nodo que creó la
171
+ * pendiente —Gmail primero, Email de respaldo, porque `sendAndWaitForResponse`
172
+ * pasó al nodo de Gmail al repartirse y los pendientes de antes apuntan a la
173
+ * fila vieja—. Es UN booleano, así que se pide en vez de traerse el nodo: el
174
+ * gateway sólo tiene que pintar la página.
175
+ *
176
+ * ⚠️ Y va en su propio método, no en `AprobacionPendiente`: sólo hace falta
177
+ * en el camino de rechazo, y meterlo en la fila obligaría a dos búsquedas
178
+ * extra en CADA lectura.
179
+ */
180
+ pideComentarioAlRechazar(pendingId: string): Promise<{
181
+ /** Si es `false`, el rechazo sigue siendo de un clic. */
182
+ pide: boolean;
183
+ /** Los dos textos de la página, tal y como los configuró quien monta el
184
+ * flujo. `undefined` deja el de por defecto. */
185
+ label?: string;
186
+ placeholder?: string;
187
+ }>;
188
+ /** Da de alta la aprobación de una publicación en redes. */
189
+ crearParaAprobacionDeRedes(params: {
190
+ sourceNodeId: string;
191
+ eventId: string;
192
+ webhookId: string;
193
+ orgId: string;
194
+ payload: Record<string, unknown>;
195
+ headers: Record<string, unknown>;
196
+ plan: Record<string, unknown>;
197
+ timeoutMinutes: number;
198
+ }): Promise<{
199
+ id: string;
200
+ resolveToken: string;
201
+ }>;
202
+ }
203
+ /**
204
+ * ⚠️ Cadena y no `Symbol`, igual que los otros tokens de esta fase: un
205
+ * `Symbol` no sobrevive a una frontera de módulo duplicada.
206
+ */
207
+ export declare const APROBACIONES_PENDIENTES = "APROBACIONES_PENDIENTES";
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Lo que una PUERTA del gateway necesita de las aprobaciones pendientes.
4
+ *
5
+ * ## Por qué existe
6
+ *
7
+ * `source-nodes/triggers` —el trigger de Telegram— y `social-posts` viven en el
8
+ * gateway; `approval-nodes` se muda a hw-nodes. Entre los dos había cuatro
9
+ * llamadas directas a `ApprovalNodesService`, y una de ellas pasaba un
10
+ * **documento de Mongoose vivo** al motor.
11
+ *
12
+ * ## 🔥 Por qué el contrato NO es «los mismos cuatro métodos»
13
+ *
14
+ * El código de hoy hace esto:
15
+ *
16
+ * const fila = await pendingService.resolvePendingByToken({…});
17
+ * await eventPipeline.resumeAfterSendAndWait(fila, {…});
18
+ *
19
+ * O sea: la puerta resuelve la fila y le pasa el DOCUMENTO al motor, que le lee
20
+ * seis campos más (`eventId`, `organizationId`, `webhookId`, `payload`,
21
+ * `eventHeaders`, `id`). Un documento de Mongoose no cruza una red, así que
22
+ * copiar esos métodos tal cual obligaría a serializar la fila entera y a
23
+ * mantener las dos formas en paralelo.
24
+ *
25
+ * Pero es que no hace falta: **el motor y las aprobaciones están del MISMO
26
+ * lado**. Los dos se van a hw-nodes. Lo único que cruza es la decisión —quién
27
+ * pulsó, cuándo, qué escribió—, y el documento se queda donde vive.
28
+ *
29
+ * Por eso hay un solo método para las dos cosas, `resolverYReanudar`.
30
+ *
31
+ * ## ⚠️ La distinción que hay que conservar
32
+ *
33
+ * Hoy los dos fallos NO se tratan igual, y eso es visible para el usuario:
34
+ *
35
+ * - si no se puede RESOLVER la fila, a Telegram le vuelve «No pude registrar
36
+ * tu respuesta. Inténtalo de nuevo»;
37
+ * - si falla el REANUDADO, se registra y ya: la fila está resuelta, el
38
+ * usuario ve su confirmación, y volver a lanzar aguas abajo necesitaría a
39
+ * un operador de todas formas.
40
+ *
41
+ * Por eso `resolverYReanudar` devuelve un booleano que habla SÓLO de la
42
+ * resolución. Fundir los dos fallos en uno cambiaría lo que ve el usuario sin
43
+ * tocar una línea de la UI.
44
+ */
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.APROBACIONES_PENDIENTES = void 0;
47
+ /**
48
+ * ⚠️ Cadena y no `Symbol`, igual que los otros tokens de esta fase: un
49
+ * `Symbol` no sobrevive a una frontera de módulo duplicada.
50
+ */
51
+ exports.APROBACIONES_PENDIENTES = 'APROBACIONES_PENDIENTES';
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Lo que un nodo le manda decir al panel. Nada más.
3
+ *
4
+ * ## Por qué
5
+ *
6
+ * Costura de la Fase 2. `nodes/` y `common/` importaban `ingress/` 18 veces, y
7
+ * las 18 apuntaban a la MISMA clase:
8
+ *
9
+ * 10 `WebhookGateway` el gateway de Socket.IO
10
+ * 8 `WebhookGatewayModule` cableado, desaparece con la mudanza
11
+ *
12
+ * `WebhookGateway` es un `@WebSocketGateway` de 2000 líneas: mantiene el
13
+ * servidor de sockets, las salas por organización, la autenticación de cada
14
+ * conexión —con sus modelos de `Token`, `User` y `ApiKey`—, el túnel de
15
+ * desarrollo y las peticiones HTTP en espera. Nada de eso puede vivir en
16
+ * `hw-nodes`: quien tiene abiertos los sockets del panel es el gateway.
17
+ *
18
+ * Lo que el nodo necesita es mucho menos: **decir que algo ha pasado**.
19
+ *
20
+ * ## Lo que ya estaba bien
21
+ *
22
+ * Los diez métodos reciben datos planos, y no por casualidad: los llamantes
23
+ * ya hacen `.toJSON()` o pasan por `comoObjetoPlano(...)` antes de emitir.
24
+ * Aquí no hay ningún documento de Mongoose que aplanar — algo que en la
25
+ * costura de ficheros costó un adaptador entero.
26
+ *
27
+ * ⚠️ Por eso las cargas se declaran `object` y NO `Record<string, unknown>`,
28
+ * que fue lo primero que escribí. Una firma de índice hace el contrato MÁS
29
+ * ESTRICTO que la realidad: rechaza lo que devuelve `.toJSON()` de Mongoose
30
+ * —que no la tiene— y también una interfaz declarada como `PipelineStep`.
31
+ * Trece errores de compilación que no señalaban ningún dato mal formado,
32
+ * sólo un contrato mal escrito. Es la segunda vez en esta fase.
33
+ *
34
+ * ⚠️ Y una que el inventario evitó: un `grep` de `gateway.<algo>` sacaba
35
+ * `subscribe` y `unsubscribe` de más. Son de `DiscordGatewayService`, otra
36
+ * clase que en `discord.provider` también se llama `gateway`. Medir por el
37
+ * TIPO inyectado y no por el nombre de la propiedad es lo que los descarta.
38
+ *
39
+ * ## ⚠️ Nueve avisan, uno CONTESTA
40
+ *
41
+ * Nueve de los diez son difusión a la sala `org:<id>`: se mandan y no se
42
+ * espera nada. Si se pierde uno, el panel enseña un dato viejo hasta que
43
+ * alguien recargue — molesto y nada más.
44
+ *
45
+ * `resolveSyncWaiter` no. Ése completa una **petición HTTP que el gateway
46
+ * tiene retenida** (el modo síncrono de un webhook): al otro lado hay alguien
47
+ * esperando una respuesta. Si ese mensaje se pierde, el que llamó se queda
48
+ * colgado hasta que salte el 408 de `waitForSync`.
49
+ *
50
+ * Va en el mismo contrato porque hoy es la misma inyección y su único
51
+ * llamante es el motor, pero quien implemente esto por red tiene que tratarlo
52
+ * distinto: los nueve pueden ser «dispara y olvida», éste no.
53
+ */
54
+ /** Un aviso en vivo al panel de la organización. */
55
+ export interface AvisosEnVivo {
56
+ emitNewEvent(webhookId: string, orgId: string, evento: object): void;
57
+ emitEventUpdate(webhookId: string, orgId: string, evento: object): void;
58
+ emitWebhookUpdate(webhookId: string, orgId: string, webhook: object): void;
59
+ /** Un paso de pipeline terminó. */
60
+ emitPipelineStep(orgId: string, paso: object): void;
61
+ /**
62
+ * Un nodo FUENTE recibió algo.
63
+ *
64
+ * Evento propio y no `pipeline:step` porque un webhook recibiendo tráfico
65
+ * no ejecuta un paso, y fabricarle uno falso ensucia a quien escucha pasos
66
+ * de verdad.
67
+ */
68
+ emitNodePayload(orgId: string, data: {
69
+ nodeType: string;
70
+ nodeId: string;
71
+ lastPayload: unknown;
72
+ /** Datos de ejemplo adoptados a mano; viaja aparte del payload. */
73
+ example?: boolean;
74
+ }): void;
75
+ /** Un trigger acaba de recibir algo de su proveedor. */
76
+ emitTriggerActivity(orgId: string, data: {
77
+ nodeId: string;
78
+ lastPushAt: string;
79
+ }): void;
80
+ emitApprovalUpdate(orgId: string, data: {
81
+ type: 'created' | 'resolved';
82
+ pendingApproval: object;
83
+ }): void;
84
+ emitSwRunComplete(orgId: string, swId: string, run: object): void;
85
+ /**
86
+ * Un contador de uso cambió. Sin payload: quien escucha vuelve a pedir
87
+ * `GET /auth/me/usage`.
88
+ */
89
+ emitUsageInvalidate(orgId: string): void;
90
+ /**
91
+ * ⚠️ Contesta una petición HTTP que el gateway tiene RETENIDA.
92
+ *
93
+ * No es difusión. Al otro lado hay un cliente esperando la respuesta de un
94
+ * webhook en modo síncrono; perder este mensaje lo deja colgado hasta el
95
+ * 408 de `waitForSync`. Los otros nueve pueden ser «dispara y olvida»; éste
96
+ * necesita entrega, y por eso está escrito aparte y no mezclado arriba.
97
+ */
98
+ resolveSyncWaiter(eventId: string, resultado: {
99
+ status: number;
100
+ body: Record<string, unknown>;
101
+ }): void;
102
+ }
103
+ /**
104
+ * ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
105
+ */
106
+ export declare const AVISOS_EN_VIVO = "AVISOS_EN_VIVO";
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Lo que un nodo le manda decir al panel. Nada más.
4
+ *
5
+ * ## Por qué
6
+ *
7
+ * Costura de la Fase 2. `nodes/` y `common/` importaban `ingress/` 18 veces, y
8
+ * las 18 apuntaban a la MISMA clase:
9
+ *
10
+ * 10 `WebhookGateway` el gateway de Socket.IO
11
+ * 8 `WebhookGatewayModule` cableado, desaparece con la mudanza
12
+ *
13
+ * `WebhookGateway` es un `@WebSocketGateway` de 2000 líneas: mantiene el
14
+ * servidor de sockets, las salas por organización, la autenticación de cada
15
+ * conexión —con sus modelos de `Token`, `User` y `ApiKey`—, el túnel de
16
+ * desarrollo y las peticiones HTTP en espera. Nada de eso puede vivir en
17
+ * `hw-nodes`: quien tiene abiertos los sockets del panel es el gateway.
18
+ *
19
+ * Lo que el nodo necesita es mucho menos: **decir que algo ha pasado**.
20
+ *
21
+ * ## Lo que ya estaba bien
22
+ *
23
+ * Los diez métodos reciben datos planos, y no por casualidad: los llamantes
24
+ * ya hacen `.toJSON()` o pasan por `comoObjetoPlano(...)` antes de emitir.
25
+ * Aquí no hay ningún documento de Mongoose que aplanar — algo que en la
26
+ * costura de ficheros costó un adaptador entero.
27
+ *
28
+ * ⚠️ Por eso las cargas se declaran `object` y NO `Record<string, unknown>`,
29
+ * que fue lo primero que escribí. Una firma de índice hace el contrato MÁS
30
+ * ESTRICTO que la realidad: rechaza lo que devuelve `.toJSON()` de Mongoose
31
+ * —que no la tiene— y también una interfaz declarada como `PipelineStep`.
32
+ * Trece errores de compilación que no señalaban ningún dato mal formado,
33
+ * sólo un contrato mal escrito. Es la segunda vez en esta fase.
34
+ *
35
+ * ⚠️ Y una que el inventario evitó: un `grep` de `gateway.<algo>` sacaba
36
+ * `subscribe` y `unsubscribe` de más. Son de `DiscordGatewayService`, otra
37
+ * clase que en `discord.provider` también se llama `gateway`. Medir por el
38
+ * TIPO inyectado y no por el nombre de la propiedad es lo que los descarta.
39
+ *
40
+ * ## ⚠️ Nueve avisan, uno CONTESTA
41
+ *
42
+ * Nueve de los diez son difusión a la sala `org:<id>`: se mandan y no se
43
+ * espera nada. Si se pierde uno, el panel enseña un dato viejo hasta que
44
+ * alguien recargue — molesto y nada más.
45
+ *
46
+ * `resolveSyncWaiter` no. Ése completa una **petición HTTP que el gateway
47
+ * tiene retenida** (el modo síncrono de un webhook): al otro lado hay alguien
48
+ * esperando una respuesta. Si ese mensaje se pierde, el que llamó se queda
49
+ * colgado hasta que salte el 408 de `waitForSync`.
50
+ *
51
+ * Va en el mismo contrato porque hoy es la misma inyección y su único
52
+ * llamante es el motor, pero quien implemente esto por red tiene que tratarlo
53
+ * distinto: los nueve pueden ser «dispara y olvida», éste no.
54
+ */
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.AVISOS_EN_VIVO = void 0;
57
+ /**
58
+ * ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2.
59
+ */
60
+ exports.AVISOS_EN_VIVO = 'AVISOS_EN_VIVO';
@@ -0,0 +1,78 @@
1
+ /**
2
+ * El canal por el que el ejecutor le va dando trozos a una respuesta que el
3
+ * gateway tiene RETENIDA.
4
+ *
5
+ * ## De dónde sale la forma
6
+ *
7
+ * No es difusión. Es el mismo gesto que `resolveSyncWaiter` en
8
+ * `avisos-en-vivo.ts`, y su comentario lo dice mejor que yo:
9
+ *
10
+ * «Contesta una petición HTTP que el gateway tiene RETENIDA. No es difusión.
11
+ * Al otro lado hay un cliente esperando la respuesta.»
12
+ *
13
+ * Aquí es un SSE en vez de una respuesta única: el widget de chat mantiene
14
+ * abierta una conexión y el nodo de IA le manda tokens según los produce.
15
+ *
16
+ * ## ⚠️ Por qué NO se reusa `AVISOS_EN_VIVO`
17
+ *
18
+ * Porque ese contrato es «un aviso en vivo **al panel de la organización**» y
19
+ * todo lo suyo va con `orgId`. El visitante de un chat NO es el panel: es
20
+ * alguien anónimo en la web de un cliente. Mandar sus tokens por una sala de
21
+ * organización sería una fuga.
22
+ *
23
+ * La clave aquí es el STREAM, no la organización.
24
+ *
25
+ * ## ⚠️ Y por qué el gateway se queda el SSE
26
+ *
27
+ * Se valoró que el widget conectara directo al ejecutor. Se descartó: la puerta
28
+ * del chat es `@Public()` —sin sesión, porque es un widget en la web de un
29
+ * cliente—, así que eso le pondría una puerta pública y sin autenticar al
30
+ * servicio que corre el Code Node con código del usuario. Y habría que
31
+ * duplicar el control de orígenes, que ya avisa de que «un cliente que no sea
32
+ * un navegador ignora las cabeceras».
33
+ *
34
+ * Una sola puerta pública. El coste es un salto por trozo sobre conexión
35
+ * persistente, y el ritmo lo pone el modelo, no la red.
36
+ */
37
+ /**
38
+ * Lo que viaja por el stream.
39
+ *
40
+ * ⚠️ Se declara AQUÍ y no se importa de `ai-nodes`: es lo que cruza la
41
+ * frontera, así que su forma es parte del contrato y no del ejecutor. Y son
42
+ * datos planos —cadenas, números, un booleano—, que es lo que hace que pueda
43
+ * cruzar.
44
+ */
45
+ export type EventoDeStream = {
46
+ event: 'token';
47
+ data: {
48
+ text: string;
49
+ };
50
+ } | {
51
+ event: 'tool_start';
52
+ data: {
53
+ name: string;
54
+ input: Record<string, unknown>;
55
+ };
56
+ } | {
57
+ event: 'tool_end';
58
+ data: {
59
+ name: string;
60
+ output: string;
61
+ success: boolean;
62
+ durationMs: number;
63
+ };
64
+ };
65
+ /**
66
+ * Lo que escribe un trozo en el SSE. Es un callback y por eso NO viaja: quien
67
+ * lo tiene es el gateway, que sostiene la respuesta.
68
+ */
69
+ export type EmisorDeStream = (evento: EventoDeStream) => void | Promise<void>;
70
+ export interface CanalDeStream {
71
+ /**
72
+ * Un trozo para ese stream. Si nadie lo escucha, se tira sin ruido: quien
73
+ * cerró la pestaña no es un error.
74
+ */
75
+ emitir(streamId: string, evento: EventoDeStream): void;
76
+ }
77
+ /** ⚠️ Cadena y no `Symbol`. */
78
+ export declare const CANAL_DE_STREAM = "CANAL_DE_STREAM";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CANAL_DE_STREAM = void 0;
4
+ /** ⚠️ Cadena y no `Symbol`. */
5
+ exports.CANAL_DE_STREAM = 'CANAL_DE_STREAM';