@dotrino/remote-agent 0.18.1 → 0.18.3
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 +1 -1
- package/src/agent.js +18 -9
- package/src/client.js +17 -8
- package/src/discover.js +11 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/remote-agent",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
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/src/agent.js
CHANGED
|
@@ -31,7 +31,7 @@ import { HS, ACK, DATA, PING, PONG, ERROR, VMSG, SIGN_SCOPE, SESSION_TTL_MS, SES
|
|
|
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`. */
|
|
34
|
-
class AgentSession {
|
|
34
|
+
export class AgentSession {
|
|
35
35
|
constructor ({ sid, key, from, device, send, isDirect = null }) {
|
|
36
36
|
this.sid = sid
|
|
37
37
|
this.key = key
|
|
@@ -44,10 +44,16 @@ class AgentSession {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Envía un payload de dominio cifrado al cliente. */
|
|
47
|
-
|
|
48
|
-
if (this._closed) return
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
send (payload) {
|
|
48
|
+
if (this._closed) return Promise.resolve()
|
|
49
|
+
// SALEN EN EL ORDEN EN QUE SE PIDIERON. Cifrar tarda según el tamaño: sin esta cola, un
|
|
50
|
+
// mensaje pequeño adelantaba a uno grande pedido antes (en la terminal, lo que la consola
|
|
51
|
+
// pintaba llegaba antes que su pantalla inicial, y esta lo tapaba después).
|
|
52
|
+
const sealed = seal(this.key, payload).catch(() => null)
|
|
53
|
+
this._out = (this._out || Promise.resolve()).then(() => sealed).then((env) => {
|
|
54
|
+
if (env && !this._closed) this._send(this.from, { type: DATA, sid: this.sid, env })
|
|
55
|
+
})
|
|
56
|
+
return this._out
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -79,10 +85,13 @@ class AgentSession {
|
|
|
79
85
|
|
|
80
86
|
on (ev, cb) { this._h[ev]?.push(cb); return this }
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/** Lo que llega se entrega en el orden en que llegó, tarde lo que tarde en descifrarse cada uno. */
|
|
89
|
+
_ingest (env) {
|
|
90
|
+
const opened = open(this.key, env).then((msg) => ({ msg }), () => null)
|
|
91
|
+
this._in = (this._in || Promise.resolve()).then(() => opened).then((r) => {
|
|
92
|
+
if (r) for (const h of this._h.message) { try { h(r.msg) } catch (_) {} }
|
|
93
|
+
})
|
|
94
|
+
return this._in
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
close () {
|
package/src/client.js
CHANGED
|
@@ -115,10 +115,13 @@ export class RemoteAgentClient {
|
|
|
115
115
|
if (token === this.agentToken) this.agentToken = null
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
-
this.client.on('message',
|
|
118
|
+
this.client.on('message', (_from, p) => {
|
|
119
119
|
if (!p || typeof p !== 'object') return
|
|
120
120
|
if (p.type === DATA && p.sid === this.sid) {
|
|
121
|
-
|
|
121
|
+
// Se entregan en el orden en que llegaron: descifrar tarda según el tamaño, y sin
|
|
122
|
+
// esta cola un mensaje pequeño adelantaba a uno grande que llegó antes.
|
|
123
|
+
const opened = open(this.key, p.env).then((m) => ({ m }), () => null)
|
|
124
|
+
this._in = (this._in || Promise.resolve()).then(() => opened).then((r) => { if (r) this._emit('message', r.m) })
|
|
122
125
|
} else if (p.type === ERROR) {
|
|
123
126
|
this._onAgentError(p)
|
|
124
127
|
}
|
|
@@ -214,12 +217,18 @@ export class RemoteAgentClient {
|
|
|
214
217
|
async send (payload) {
|
|
215
218
|
if (this._resuming) await this._resuming
|
|
216
219
|
if (!this.key) throw Object.assign(new Error('no session with the agent'), { code: 'no-session' })
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
// Salen en el orden en que se pidieron, tarde lo que tarde en cifrarse cada uno.
|
|
221
|
+
const sid = this.sid
|
|
222
|
+
const sealed = seal(this.key, payload)
|
|
223
|
+
const turn = (this._out || Promise.resolve()).then(() => sealed).then((env) => {
|
|
224
|
+
const msg = { type: DATA, sid, env }
|
|
225
|
+
// Por token en cuanto se sabe: sube al canal directo, y si el token murió el pilar lo
|
|
226
|
+
// saca por la pubkey. Por pubkey solo mientras no hay token.
|
|
227
|
+
if (this.agentToken) this.client.sendToOrQueue(this.agentToken, msg, { peerPubkey: this.agentPubkey })
|
|
228
|
+
else this.client.sendByPubkey(this.agentPubkey, msg)
|
|
229
|
+
})
|
|
230
|
+
this._out = turn.catch(() => {})
|
|
231
|
+
return turn
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
/** Sonda de presencia (liveness) sin abrir sesión. Devuelve true si responde. */
|
package/src/discover.js
CHANGED
|
@@ -29,14 +29,22 @@ import { PING, PONG } from '../protocol.js'
|
|
|
29
29
|
* `'ia-agent'`). Omitir = todos los que tengan nameOf y no sean `'cli'` (un navegador
|
|
30
30
|
* enrolado queda como `cli` y no atiende a nadie).
|
|
31
31
|
* @returns {Promise<Array<{sub:string,label:string,cn:string|null}>>} uno por miembro.
|
|
32
|
+
* @throws `code: 'no-acta'` si ni la bóveda ni lo guardado dan el acta: no se sabe, y no es «ninguno».
|
|
32
33
|
*/
|
|
33
34
|
export async function listAgentsByLabel (id, label) {
|
|
34
35
|
// Por su efecto: trae el acta vigente y la adopta. La lista de aparatos que devuelva —si
|
|
35
36
|
// devuelve alguna— no se mira: quién es de la cuenta lo dice el acta.
|
|
36
37
|
let acta = null
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
let why = null
|
|
39
|
+
try { acta = (await id.listVaultDevices())?.acta || null } catch (e) { why = e }
|
|
40
|
+
// La bóveda no contestó: vale el acta que ya se tiene guardada.
|
|
41
|
+
if (!acta) acta = (await id.profileActa?.().catch((e) => { why = why || e; return null }))?.acta || null
|
|
42
|
+
// SIN ACTA NO SE SABE quién es de la cuenta, y eso no es «no hay nadie». Devolver una lista
|
|
43
|
+
// vacía aquí hacía que una máquina con la bóveda inalcanzable dijera «no tienes otras
|
|
44
|
+
// máquinas» con toda tranquilidad. Se para y se dice.
|
|
45
|
+
if (!acta) {
|
|
46
|
+
throw Object.assign(new Error(`could not get the account record (acta) from the vault${why ? `: ${why.message}` : ''}`), { code: 'no-acta', cause: why || undefined })
|
|
47
|
+
}
|
|
40
48
|
|
|
41
49
|
const mine = id.me?.publickey
|
|
42
50
|
const nameOf = (m) => m.cn || m.label || null
|