@dotrino/remote-agent 0.18.0 → 0.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/remote-agent",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Middleware compartido del ecosistema Dotrino para apps de agente remoto cifrado punto a punto (handshake E2E, emparejamiento SAS con vault, revocación, descubrimiento). Lo consumen dotrino-terminal (PTY) y dotrino-ia (chat con IA).",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/protocol.js CHANGED
@@ -39,6 +39,11 @@ export const SIGN_SCOPE = 'vault:sign'
39
39
  /** Vigencia de una sesión inactiva y refresco de la lista de revocados. */
40
40
  export const SESSION_TTL_MS = 30 * 60 * 1000
41
41
  export const REVOKE_REFRESH_MS = 5 * 60 * 1000
42
+ /**
43
+ * Cuánto se espera a un cliente cuya conexión se fue antes de cerrarle la sesión. Un cliente
44
+ * que reconecta sigue con su sesión desde un token nuevo: si en este plazo manda algo, se queda.
45
+ */
46
+ export const SESSION_GONE_MS = 20 * 1000
42
47
 
43
48
  /**
44
49
  * RENOVACIÓN del cert de esta máquina. El cert dura 30 días; se pide uno fresco
package/src/agent.js CHANGED
@@ -27,7 +27,7 @@ import { adoptRecord } from './record.js'
27
27
  import { installNodeGlobals } from '../node-globals.js'
28
28
  import { makeEphemeral, deriveKey, seal, open } from '../e2e.js'
29
29
  import { makeCatchUp } from './catch-up.js'
30
- import { HS, ACK, DATA, PING, PONG, ERROR, VMSG, SIGN_SCOPE, SESSION_TTL_MS, REVOKE_REFRESH_MS } from '../protocol.js'
30
+ import { HS, ACK, DATA, PING, PONG, ERROR, VMSG, SIGN_SCOPE, SESSION_TTL_MS, SESSION_GONE_MS, REVOKE_REFRESH_MS } from '../protocol.js'
31
31
  import { loadLink, saveLink, dataDir } from './link.js'
32
32
 
33
33
  /** Sesión cifrada con un dispositivo cliente. La app recibe esto en `onSession`. */
@@ -88,6 +88,7 @@ class AgentSession {
88
88
  close () {
89
89
  if (this._closed) return
90
90
  this._closed = true
91
+ clearTimeout(this._gone)
91
92
  for (const h of this._h.close) { try { h() } catch (_) {} }
92
93
  }
93
94
  }
@@ -105,6 +106,7 @@ class AgentSession {
105
106
  * @param {()=>void} [opts.onRevoked] se llamó al auto-borrarse por revocación.
106
107
  * @param {()=>void} [opts.onReady] agente listo y escuchando.
107
108
  * @param {boolean} [opts.quiet] sin logs.
109
+ * @param {number} [opts.sessionGoneMs] cuánto se espera a un cliente cuya conexión se fue (`SESSION_GONE_MS`).
108
110
  * @param {number} [opts.netLogMs] cada cuánto van al log las estadísticas de red (5 min).
109
111
  * @param {object} [opts.client] transporte ya conectado (SOLO pruebas).
110
112
  *
@@ -420,9 +422,27 @@ export async function startRemoteAgent (opts = {}) {
420
422
  if (!s) return send(from, { type: ERROR, code: 'unknown-session', sid: p.sid, error: 'sesión desconocida o expirada' })
421
423
  s._exp = Date.now() + SESSION_TTL_MS
422
424
  s.from = from
425
+ if (s._gone) { clearTimeout(s._gone); s._gone = null }
423
426
  s._ingest(p.env).catch(() => send(from, { type: ERROR, error: 'sobre inválido' }))
424
427
  }
425
428
 
429
+ // LA CONEXIÓN DEL CLIENTE SE FUE: su sesión no se queda 30 minutos. El proxio avisa de que ese
430
+ // token murió (`peer_disconnected`); un token no vuelve, así que quien sigue vivo manda desde
431
+ // otro y la sesión se queda (`handleData`). Si en `SESSION_GONE_MS` no llega nada, se cierra.
432
+ // Antes solo vencía por `SESSION_TTL_MS`: un teléfono que volvía del fondo abría sesión nueva
433
+ // y la vieja seguía enganchada a su consola (seis del mismo aparato en una, el 2026-10-09).
434
+ const goneMs = opts.sessionGoneMs ?? SESSION_GONE_MS
435
+ client.on('peer_disconnected', (token) => {
436
+ for (const [sid, s] of sessions) {
437
+ if (s.from !== token || s._gone) continue
438
+ s._gone = setTimeout(() => {
439
+ s.close(); sessions.delete(sid)
440
+ audit('session-gone', { sid: sid.slice(0, 8) })
441
+ }, goneMs)
442
+ s._gone.unref?.()
443
+ }
444
+ })
445
+
426
446
  // POR DÓNDE VA EL TRÁFICO, AL LOG. Un agente no tiene topbar: sin esto, saber si una
427
447
  // sesión subió a WebRTC o sigue dando la vuelta por el proxio pedía una prueba aparte.
428
448
  // Lo escribe el pilar (`logStats`), que lee los clientes del proceso; aquí solo se enciende.