@hostwebhook/platform-contracts 0.1.0 → 0.3.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/README.md +64 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/operadores.d.ts +60 -0
- package/dist/operadores.js +123 -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
|
@@ -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,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Las conversaciones de un chat trigger, por repositorio.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué costó más que las otras trece
|
|
5
|
+
*
|
|
6
|
+
* Las demás costuras invertían un servicio. Ésta y la de entregas invierten el
|
|
7
|
+
* acceso directo a una COLECCIÓN, y ésta además **mutaba el documento vivo**:
|
|
8
|
+
*
|
|
9
|
+
* const existing = await this.conversationModel.findOne({...});
|
|
10
|
+
* existing.messages.push(userMsg);
|
|
11
|
+
* existing.expiresAt = expiresAt;
|
|
12
|
+
* await existing.save();
|
|
13
|
+
*
|
|
14
|
+
* Eso no es una consulta que se pueda mover: es un documento de Mongoose
|
|
15
|
+
* cambiando en memoria y guardándose. Por eso quedó fuera del lote del #468 —
|
|
16
|
+
* cerrarla obliga a decidir de quién es la colección.
|
|
17
|
+
*
|
|
18
|
+
* La respuesta, mirando lo que se hace con ella: es del gateway. El panel
|
|
19
|
+
* lista conversaciones, la retención se administra desde `plans/` con
|
|
20
|
+
* `chatConversationRetentionDays`, y el índice TTL vive en su entidad. El nodo
|
|
21
|
+
* escribe turnos y los vuelve a leer.
|
|
22
|
+
*
|
|
23
|
+
* ## Y de paso arregla algo que ya estaba anotado
|
|
24
|
+
*
|
|
25
|
+
* El llamante de `upsert` tenía este comentario:
|
|
26
|
+
*
|
|
27
|
+
* *«Plainify each entry so we don't leak Mongoose subdocument refs (parent,
|
|
28
|
+
* `$__`, `_doc`, …) into the payload — those circular refs blow up recursive
|
|
29
|
+
* walkers like extractAttachments / template renderers with "Maximum call
|
|
30
|
+
* stack size exceeded"».*
|
|
31
|
+
*
|
|
32
|
+
* O sea que ya se estaba aplanando a mano, después de recibir el documento,
|
|
33
|
+
* porque no hacerlo reventaba. El repositorio devuelve datos planos y ese
|
|
34
|
+
* peligro desaparece en origen en vez de esquivarse en cada llamante.
|
|
35
|
+
*
|
|
36
|
+
* ## ⚠️ Lo que no se puede perder
|
|
37
|
+
*
|
|
38
|
+
* - **`expiresAt` se refresca en CADA turno de usuario.** La ventana de
|
|
39
|
+
* retención rueda con la actividad; sin eso, una sesión larga caduca a mitad
|
|
40
|
+
* de conversación. `null` significa «no caduca» (plan enterprise).
|
|
41
|
+
* - **El dedupe de voz.** Los SDK de voz repiten la transcripción final, así
|
|
42
|
+
* que un turno idéntico al último —mismo rol y mismo contenido recortado— no
|
|
43
|
+
* se añade. Sin eso el transcript sale duplicado.
|
|
44
|
+
* - **Los borradores pendientes son la memoria entre turnos.** Es lo que deja
|
|
45
|
+
* al modelo entender un «confirma» que llega en el turno siguiente.
|
|
46
|
+
*/
|
|
47
|
+
/** Un turno del transcript. `mode` ausente significa texto. */
|
|
48
|
+
export interface MensajeDeConversacion {
|
|
49
|
+
role: string;
|
|
50
|
+
content: string;
|
|
51
|
+
mode?: 'voice';
|
|
52
|
+
}
|
|
53
|
+
/** Un borrador que quedó a medias y espera confirmación. */
|
|
54
|
+
export interface BorradorPendiente {
|
|
55
|
+
draftId: string;
|
|
56
|
+
operation: string;
|
|
57
|
+
subject?: string;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
}
|
|
60
|
+
/** Una conversación, plana. */
|
|
61
|
+
export interface Conversacion {
|
|
62
|
+
id: string;
|
|
63
|
+
sessionId: string;
|
|
64
|
+
title?: string;
|
|
65
|
+
messages: MensajeDeConversacion[];
|
|
66
|
+
pendingDrafts: BorradorPendiente[];
|
|
67
|
+
createdAt?: Date;
|
|
68
|
+
updatedAt?: Date;
|
|
69
|
+
}
|
|
70
|
+
export interface RepositorioDeConversaciones {
|
|
71
|
+
/** La lista paginada del panel, con su total. */
|
|
72
|
+
listarPorTrigger(triggerId: string, opts: {
|
|
73
|
+
offset: number;
|
|
74
|
+
limit: number;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
datos: Conversacion[];
|
|
77
|
+
total: number;
|
|
78
|
+
}>;
|
|
79
|
+
/** ⚠️ `null` si no existe o no es de ese trigger; el llamante decide el 404. */
|
|
80
|
+
buscarPorId(conversationId: string, triggerId: string): Promise<Conversacion | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Un turno de usuario: lo añade a la sesión, o la crea.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ `expiresAt` se escribe SIEMPRE, exista ya la conversación o no: es lo
|
|
85
|
+
* que hace rodar la ventana de retención con la actividad.
|
|
86
|
+
*/
|
|
87
|
+
guardarTurnoDeUsuario(params: {
|
|
88
|
+
orgId: string;
|
|
89
|
+
chatTriggerId: string;
|
|
90
|
+
sessionId: string;
|
|
91
|
+
title?: string;
|
|
92
|
+
mensaje: MensajeDeConversacion;
|
|
93
|
+
/** `null` = no caduca. */
|
|
94
|
+
expiresAt: Date | null;
|
|
95
|
+
}): Promise<Conversacion>;
|
|
96
|
+
/**
|
|
97
|
+
* Un turno de voz.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ Devuelve `anadido: false` cuando el turno es idéntico al último —mismo
|
|
100
|
+
* rol y mismo contenido recortado—. Los SDK de voz repiten la transcripción
|
|
101
|
+
* final y sin eso el transcript sale duplicado.
|
|
102
|
+
*/
|
|
103
|
+
guardarTurnoDeVoz(params: {
|
|
104
|
+
orgId: string;
|
|
105
|
+
chatTriggerId: string;
|
|
106
|
+
sessionId: string;
|
|
107
|
+
title: string;
|
|
108
|
+
mensaje: MensajeDeConversacion;
|
|
109
|
+
expiresAt: Date | null;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
conversationId: string;
|
|
112
|
+
anadido: boolean;
|
|
113
|
+
}>;
|
|
114
|
+
/** La respuesta del asistente, al final del transcript. */
|
|
115
|
+
anadirRespuesta(conversationId: string, content: string): Promise<void>;
|
|
116
|
+
/** Lo que el widget relee al recargar la página a mitad de sesión. */
|
|
117
|
+
historialDeSesion(chatTriggerId: string, sessionId: string): Promise<{
|
|
118
|
+
conversationId: string | null;
|
|
119
|
+
messages: MensajeDeConversacion[];
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Aplica lo que pasó con los borradores en un turno y devuelve cómo quedó.
|
|
123
|
+
*
|
|
124
|
+
* ⚠️ Se quitan los enviados y se añaden los nuevos EN ESE ORDEN, y devuelve
|
|
125
|
+
* los ids resultantes porque el llamante los registra: sin ese log no había
|
|
126
|
+
* forma de averiguar por qué el modelo vio el borrador equivocado en el
|
|
127
|
+
* turno siguiente.
|
|
128
|
+
*/
|
|
129
|
+
aplicarEventosDeBorrador(conversationId: string, cambios: {
|
|
130
|
+
quitar: string[];
|
|
131
|
+
anadir: Array<{
|
|
132
|
+
draftId: string;
|
|
133
|
+
operation: string;
|
|
134
|
+
subject?: string;
|
|
135
|
+
}>;
|
|
136
|
+
}): Promise<string[]>;
|
|
137
|
+
}
|
|
138
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
139
|
+
export declare const CONVERSACIONES_DEL_CHAT = "CONVERSACIONES_DEL_CHAT";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Las conversaciones de un chat trigger, por repositorio.
|
|
4
|
+
*
|
|
5
|
+
* ## Por qué costó más que las otras trece
|
|
6
|
+
*
|
|
7
|
+
* Las demás costuras invertían un servicio. Ésta y la de entregas invierten el
|
|
8
|
+
* acceso directo a una COLECCIÓN, y ésta además **mutaba el documento vivo**:
|
|
9
|
+
*
|
|
10
|
+
* const existing = await this.conversationModel.findOne({...});
|
|
11
|
+
* existing.messages.push(userMsg);
|
|
12
|
+
* existing.expiresAt = expiresAt;
|
|
13
|
+
* await existing.save();
|
|
14
|
+
*
|
|
15
|
+
* Eso no es una consulta que se pueda mover: es un documento de Mongoose
|
|
16
|
+
* cambiando en memoria y guardándose. Por eso quedó fuera del lote del #468 —
|
|
17
|
+
* cerrarla obliga a decidir de quién es la colección.
|
|
18
|
+
*
|
|
19
|
+
* La respuesta, mirando lo que se hace con ella: es del gateway. El panel
|
|
20
|
+
* lista conversaciones, la retención se administra desde `plans/` con
|
|
21
|
+
* `chatConversationRetentionDays`, y el índice TTL vive en su entidad. El nodo
|
|
22
|
+
* escribe turnos y los vuelve a leer.
|
|
23
|
+
*
|
|
24
|
+
* ## Y de paso arregla algo que ya estaba anotado
|
|
25
|
+
*
|
|
26
|
+
* El llamante de `upsert` tenía este comentario:
|
|
27
|
+
*
|
|
28
|
+
* *«Plainify each entry so we don't leak Mongoose subdocument refs (parent,
|
|
29
|
+
* `$__`, `_doc`, …) into the payload — those circular refs blow up recursive
|
|
30
|
+
* walkers like extractAttachments / template renderers with "Maximum call
|
|
31
|
+
* stack size exceeded"».*
|
|
32
|
+
*
|
|
33
|
+
* O sea que ya se estaba aplanando a mano, después de recibir el documento,
|
|
34
|
+
* porque no hacerlo reventaba. El repositorio devuelve datos planos y ese
|
|
35
|
+
* peligro desaparece en origen en vez de esquivarse en cada llamante.
|
|
36
|
+
*
|
|
37
|
+
* ## ⚠️ Lo que no se puede perder
|
|
38
|
+
*
|
|
39
|
+
* - **`expiresAt` se refresca en CADA turno de usuario.** La ventana de
|
|
40
|
+
* retención rueda con la actividad; sin eso, una sesión larga caduca a mitad
|
|
41
|
+
* de conversación. `null` significa «no caduca» (plan enterprise).
|
|
42
|
+
* - **El dedupe de voz.** Los SDK de voz repiten la transcripción final, así
|
|
43
|
+
* que un turno idéntico al último —mismo rol y mismo contenido recortado— no
|
|
44
|
+
* se añade. Sin eso el transcript sale duplicado.
|
|
45
|
+
* - **Los borradores pendientes son la memoria entre turnos.** Es lo que deja
|
|
46
|
+
* al modelo entender un «confirma» que llega en el turno siguiente.
|
|
47
|
+
*/
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.CONVERSACIONES_DEL_CHAT = void 0;
|
|
50
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
51
|
+
exports.CONVERSACIONES_DEL_CHAT = 'CONVERSACIONES_DEL_CHAT';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que el motor necesita del flujo programado. UN método.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué
|
|
5
|
+
*
|
|
6
|
+
* `pipeline-run` llama a `ScheduledWorkflowsService.recordRun(swId, run)` al
|
|
7
|
+
* terminar una corrida que arrancó en un flujo programado. Es la única
|
|
8
|
+
* llamada: medido.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ Y NO es un `lastRunAt` que se pueda escribir a mano desde aquí. `recordRun`
|
|
11
|
+
* además archiva la fila en `scheduledworkflowruns` y poda a las últimas cien.
|
|
12
|
+
* Reimplementarlo del lado del motor sería una segunda copia de esa regla.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ Tiene que TERMINAR antes de que salga el marco `complete`: el panel
|
|
15
|
+
* relee el flujo cuando ese marco llega, así que apuntar después es una carrera
|
|
16
|
+
* que se pierde a veces. Por eso se espera, a diferencia de la salud del
|
|
17
|
+
* webhook.
|
|
18
|
+
*/
|
|
19
|
+
export interface CorridasProgramadas {
|
|
20
|
+
apuntarCorrida(swId: string, corrida: {
|
|
21
|
+
startedAt: Date;
|
|
22
|
+
completedAt: Date;
|
|
23
|
+
durationMs: number;
|
|
24
|
+
status: 'success' | 'partial' | 'failure';
|
|
25
|
+
errorMessage?: string | null;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/** ⚠️ Cadena y no `Symbol`. */
|
|
29
|
+
export declare const CORRIDAS_PROGRAMADAS = "CORRIDAS_PROGRAMADAS";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ¿Puede este usuario poner esta credencial? — la pregunta, sin la respuesta.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué existe
|
|
5
|
+
*
|
|
6
|
+
* Costura de la Fase 2. `node-controller.factory` aplicaba
|
|
7
|
+
* `@UseGuards(CredentialFolderGuard)` a los ~30 tipos de nodo, y ese guardia
|
|
8
|
+
* vive en `credentials/`: consulta la colección de credenciales y resuelve
|
|
9
|
+
* permisos de carpeta. Con eso, el factory —y con él todos los controladores
|
|
10
|
+
* de nodo— no se puede mudar a `hw-nodes` sin llevarse el dominio entero de
|
|
11
|
+
* credenciales, que es la Fase 4.
|
|
12
|
+
*
|
|
13
|
+
* ## Lo que NO se hace, y por qué
|
|
14
|
+
*
|
|
15
|
+
* Lo fácil era quitar el guardia y decir «ya autoriza el gateway al proxear».
|
|
16
|
+
* Y es defendible: la autenticación va EN el gateway por diseño.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ Pero este guardia existe **porque la comprobación se olvidó trece
|
|
19
|
+
* veces**. Su propio docblock cuenta el agujero: bastaba con leerle el id de
|
|
20
|
+
* credencial a un nodo que sí se pudiera ver, ponérselo a un nodo propio y
|
|
21
|
+
* ejecutarlo, y la credencial de la carpeta negada se descifraba igual. Y un
|
|
22
|
+
* id de Mongo no es un secreto: sale en el lienzo y en la barra del navegador.
|
|
23
|
+
*
|
|
24
|
+
* Dejar esa garantía viviendo SÓLO en el proxy es apostar a que nada alcance
|
|
25
|
+
* nunca `hw-nodes` por otro camino — una mala configuración, un servicio
|
|
26
|
+
* nuevo, una prueba. Cuando pase, el agujero vuelve, y vuelve callado.
|
|
27
|
+
*
|
|
28
|
+
* Así que la pregunta se queda y lo que se va es la RESPUESTA: `hw-nodes`
|
|
29
|
+
* preguntará por la red privada a quien tenga el dato. Una llamada por
|
|
30
|
+
* escritura de nodo que lleve credencial —crear o editar—, no por ejecución.
|
|
31
|
+
* Un salto interno es ~1 ms contra los 100-500 ms que el nodo gasta llamando
|
|
32
|
+
* a Slack o a Gmail.
|
|
33
|
+
*/
|
|
34
|
+
/** Los tres papeles sobre una carpeta, de menos a más. */
|
|
35
|
+
export type RolDeCarpeta = 'viewer' | 'editor' | 'owner';
|
|
36
|
+
export interface PreguntaDePermiso {
|
|
37
|
+
credentialId: string;
|
|
38
|
+
userId: string;
|
|
39
|
+
orgId: string;
|
|
40
|
+
rolMinimo: RolDeCarpeta;
|
|
41
|
+
}
|
|
42
|
+
export interface PermisoSobreCredencial {
|
|
43
|
+
/**
|
|
44
|
+
* ⚠️ Devuelve `true` cuando la credencial NO EXISTE, y eso no es un
|
|
45
|
+
* descuido: es la semántica que hay que conservar.
|
|
46
|
+
*
|
|
47
|
+
* Negar aquí daría un 403 sobre algo inexistente, y un 403 dice «existe
|
|
48
|
+
* pero no es tuya». Dejando pasar, el manejador contesta 404 como siempre y
|
|
49
|
+
* no se filtra qué ids existen — que es justo lo que este guardia protege,
|
|
50
|
+
* porque los ids circulan.
|
|
51
|
+
*
|
|
52
|
+
* O sea: `false` significa «existe y NO puedes», nunca «no la encuentro».
|
|
53
|
+
*/
|
|
54
|
+
puedeUsar(pregunta: PreguntaDePermiso): Promise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* ⚠️ Cadena y no `Symbol`. Un `Symbol` no sobrevive a una frontera de módulo
|
|
58
|
+
* duplicada ni a una serialización, y esto está pensado para acabar cruzando
|
|
59
|
+
* una red.
|
|
60
|
+
*/
|
|
61
|
+
export declare const PERMISO_SOBRE_CREDENCIAL = "PERMISO_SOBRE_CREDENCIAL";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ¿Puede este usuario poner esta credencial? — la pregunta, sin la respuesta.
|
|
4
|
+
*
|
|
5
|
+
* ## Por qué existe
|
|
6
|
+
*
|
|
7
|
+
* Costura de la Fase 2. `node-controller.factory` aplicaba
|
|
8
|
+
* `@UseGuards(CredentialFolderGuard)` a los ~30 tipos de nodo, y ese guardia
|
|
9
|
+
* vive en `credentials/`: consulta la colección de credenciales y resuelve
|
|
10
|
+
* permisos de carpeta. Con eso, el factory —y con él todos los controladores
|
|
11
|
+
* de nodo— no se puede mudar a `hw-nodes` sin llevarse el dominio entero de
|
|
12
|
+
* credenciales, que es la Fase 4.
|
|
13
|
+
*
|
|
14
|
+
* ## Lo que NO se hace, y por qué
|
|
15
|
+
*
|
|
16
|
+
* Lo fácil era quitar el guardia y decir «ya autoriza el gateway al proxear».
|
|
17
|
+
* Y es defendible: la autenticación va EN el gateway por diseño.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ Pero este guardia existe **porque la comprobación se olvidó trece
|
|
20
|
+
* veces**. Su propio docblock cuenta el agujero: bastaba con leerle el id de
|
|
21
|
+
* credencial a un nodo que sí se pudiera ver, ponérselo a un nodo propio y
|
|
22
|
+
* ejecutarlo, y la credencial de la carpeta negada se descifraba igual. Y un
|
|
23
|
+
* id de Mongo no es un secreto: sale en el lienzo y en la barra del navegador.
|
|
24
|
+
*
|
|
25
|
+
* Dejar esa garantía viviendo SÓLO en el proxy es apostar a que nada alcance
|
|
26
|
+
* nunca `hw-nodes` por otro camino — una mala configuración, un servicio
|
|
27
|
+
* nuevo, una prueba. Cuando pase, el agujero vuelve, y vuelve callado.
|
|
28
|
+
*
|
|
29
|
+
* Así que la pregunta se queda y lo que se va es la RESPUESTA: `hw-nodes`
|
|
30
|
+
* preguntará por la red privada a quien tenga el dato. Una llamada por
|
|
31
|
+
* escritura de nodo que lleve credencial —crear o editar—, no por ejecución.
|
|
32
|
+
* Un salto interno es ~1 ms contra los 100-500 ms que el nodo gasta llamando
|
|
33
|
+
* a Slack o a Gmail.
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PERMISO_SOBRE_CREDENCIAL = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* ⚠️ Cadena y no `Symbol`. Un `Symbol` no sobrevive a una frontera de módulo
|
|
39
|
+
* duplicada ni a una serialización, y esto está pensado para acabar cruzando
|
|
40
|
+
* una red.
|
|
41
|
+
*/
|
|
42
|
+
exports.PERMISO_SOBRE_CREDENCIAL = 'PERMISO_SOBRE_CREDENCIAL';
|