@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/servicios/almacenes-vectoriales.d.ts +105 -0
- package/dist/servicios/almacenes-vectoriales.js +26 -0
- package/dist/servicios/aprobaciones-pendientes.d.ts +207 -0
- package/dist/servicios/aprobaciones-pendientes.js +51 -0
- package/dist/servicios/avisos-en-vivo.d.ts +106 -0
- package/dist/servicios/avisos-en-vivo.js +60 -0
- package/dist/servicios/canal-de-stream.d.ts +78 -0
- package/dist/servicios/canal-de-stream.js +5 -0
- package/dist/servicios/conversaciones-del-chat.d.ts +139 -0
- package/dist/servicios/conversaciones-del-chat.js +51 -0
- package/dist/servicios/corridas-programadas.d.ts +29 -0
- package/dist/servicios/corridas-programadas.js +5 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.d.ts +61 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.js +42 -0
- package/dist/servicios/credenciales-del-gateway.d.ts +722 -0
- package/dist/servicios/credenciales-del-gateway.js +114 -0
- package/dist/servicios/cuentas-de-atlassian.d.ts +22 -0
- package/dist/servicios/cuentas-de-atlassian.js +5 -0
- package/dist/servicios/descarga-de-drive.d.ts +74 -0
- package/dist/servicios/descarga-de-drive.js +41 -0
- package/dist/servicios/ejecucion-de-ia.d.ts +81 -0
- package/dist/servicios/ejecucion-de-ia.js +39 -0
- package/dist/servicios/enlaces-de-flujo.d.ts +59 -0
- package/dist/servicios/enlaces-de-flujo.js +27 -0
- package/dist/servicios/ficheros-del-gateway.d.ts +210 -0
- package/dist/servicios/ficheros-del-gateway.js +8 -0
- package/dist/servicios/formas-compartidas.d.ts +57 -0
- package/dist/servicios/formas-compartidas.js +22 -0
- package/dist/servicios/historial-de-corridas.d.ts +75 -0
- package/dist/servicios/historial-de-corridas.js +7 -0
- package/dist/servicios/index.d.ts +57 -0
- package/dist/servicios/index.js +44 -0
- package/dist/servicios/limites-del-plan.d.ts +82 -0
- package/dist/servicios/limites-del-plan.js +41 -0
- package/dist/servicios/motor-de-ejecucion.d.ts +197 -0
- package/dist/servicios/motor-de-ejecucion.js +11 -0
- package/dist/servicios/plantillas-de-origen.d.ts +25 -0
- package/dist/servicios/plantillas-de-origen.js +5 -0
- package/dist/servicios/posts-sociales.d.ts +69 -0
- package/dist/servicios/posts-sociales.js +23 -0
- package/dist/servicios/registro-de-entregas.d.ts +93 -0
- package/dist/servicios/registro-de-entregas.js +46 -0
- package/dist/servicios/registro-de-eventos.d.ts +135 -0
- package/dist/servicios/registro-de-eventos.js +62 -0
- package/dist/servicios/restauracion-de-nodos.d.ts +53 -0
- package/dist/servicios/restauracion-de-nodos.js +4 -0
- package/dist/servicios/salud-del-webhook.d.ts +26 -0
- package/dist/servicios/salud-del-webhook.js +6 -0
- package/dist/servicios/secretos-de-firma.d.ts +26 -0
- package/dist/servicios/secretos-de-firma.js +5 -0
- package/dist/servicios/telemetria.d.ts +114 -0
- package/dist/servicios/telemetria.js +60 -0
- package/dist/servicios/trazas-de-llm.d.ts +79 -0
- package/dist/servicios/trazas-de-llm.js +23 -0
- package/dist/servicios/tuneles.d.ts +106 -0
- package/dist/servicios/tuneles.js +87 -0
- package/dist/servicios/workspace/acceso-al-recurso.d.ts +39 -0
- package/dist/servicios/workspace/acceso-al-recurso.js +5 -0
- package/dist/servicios/zona-horaria-del-usuario.d.ts +40 -0
- package/dist/servicios/zona-horaria-del-usuario.js +7 -0
- package/package.json +9 -3
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -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";
|