@dotrino/remote-agent 0.13.0 → 0.14.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 +10 -0
- package/package.json +1 -1
- package/src/agent.js +4 -1
- package/src/client.js +37 -5
package/README.md
CHANGED
|
@@ -141,6 +141,16 @@ chat.send({ type: 'msg', text: 'arreglá el bug en auth.js' })
|
|
|
141
141
|
const online = await chat.ping() // liveness sin abrir sesión
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
**Si el agente se reinicia, el cliente vuelve a saludar solo** (≥ 0.14.0). Las sesiones viven
|
|
145
|
+
en la memoria del agente: tras reiniciarse contesta `unknown-session` a lo que le llegue con un
|
|
146
|
+
`sid` viejo, el cliente abre otra sesión y avisa con `resumed`. Lo que la app tenía abierto allí
|
|
147
|
+
(una consola, una conversación) puede no existir ya: eso lo decide la app en ese evento. Lo que
|
|
148
|
+
se mande mientras tanto sale con la sesión nueva.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
chat.on('resumed', () => volverAEngancharme()) // p. ej. la terminal: attach, u open si ya no está
|
|
152
|
+
```
|
|
153
|
+
|
|
144
154
|
**Encontrar los agentes de UN tipo: pregúntaselo a ellos, no al nombre del acta.** El label
|
|
145
155
|
del acta lo pone el dueño al emparejar («TerminalLocal»), así que filtrar por él no encuentra
|
|
146
156
|
nada. El pong de cada agente dice qué es (el `label` con que arrancó `startRemoteAgent`):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/remote-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
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
|
@@ -317,7 +317,10 @@ export async function startRemoteAgent (opts = {}) {
|
|
|
317
317
|
|
|
318
318
|
function handleData (from, p) {
|
|
319
319
|
const s = sessions.get(p.sid)
|
|
320
|
-
|
|
320
|
+
// Con `code` y el `sid`: así el cliente sabe que es SU sesión la que se perdió (el agente
|
|
321
|
+
// se reinició, o venció) y vuelve a saludar solo, en vez de quedarse con un error. Se
|
|
322
|
+
// comprueba por el código, no por la frase.
|
|
323
|
+
if (!s) return send(from, { type: ERROR, code: 'unknown-session', sid: p.sid, error: 'sesión desconocida o expirada' })
|
|
321
324
|
s._exp = Date.now() + SESSION_TTL_MS
|
|
322
325
|
s.from = from
|
|
323
326
|
s._ingest(p.env).catch(() => send(from, { type: ERROR, error: 'sobre inválido' }))
|
package/src/client.js
CHANGED
|
@@ -41,10 +41,11 @@ export class RemoteAgentClient {
|
|
|
41
41
|
this.client = null
|
|
42
42
|
this.key = null
|
|
43
43
|
this.sid = null
|
|
44
|
-
this._h = { message: [], error: [] }
|
|
44
|
+
this._h = { message: [], error: [], resumed: [] }
|
|
45
|
+
this._resuming = null
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/** Suscribe a eventos. Devuelve un `off()` para desuscribir. Eventos: message, error. */
|
|
48
|
+
/** Suscribe a eventos. Devuelve un `off()` para desuscribir. Eventos: message, error, resumed. */
|
|
48
49
|
on (ev, cb) {
|
|
49
50
|
if (!this._h[ev]) this._h[ev] = []
|
|
50
51
|
this._h[ev].push(cb)
|
|
@@ -101,10 +102,40 @@ export class RemoteAgentClient {
|
|
|
101
102
|
if (p.type === DATA && p.sid === this.sid) {
|
|
102
103
|
try { const m = await open(this.key, p.env); this._emit('message', m) } catch {}
|
|
103
104
|
} else if (p.type === ERROR) {
|
|
104
|
-
this.
|
|
105
|
+
this._onAgentError(p)
|
|
105
106
|
}
|
|
106
107
|
})
|
|
107
108
|
|
|
109
|
+
await this._handshake()
|
|
110
|
+
return this
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Un error del agente. Si es que ya no conoce NUESTRA sesión (se reinició: las sesiones
|
|
115
|
+
* viven en su memoria), se vuelve a saludar solo y se avisa con `resumed`; lo que la app
|
|
116
|
+
* tenía abierto allí lo decide la app. Cualquier otro error, o no poder volver a saludar,
|
|
117
|
+
* sale por `error`. Un `unknown-session` de un `sid` que ya no es el nuestro (llegó tarde,
|
|
118
|
+
* de antes de volver a saludar) no dice nada nuevo y se ignora.
|
|
119
|
+
*/
|
|
120
|
+
_onAgentError (p) {
|
|
121
|
+
if (p.code !== 'unknown-session') { this._emit('error', Object.assign(new Error(p.error), { code: p.code })); return }
|
|
122
|
+
if (p.sid !== this.sid) return
|
|
123
|
+
this._resume()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Vuelve a saludar, una sola vez aunque lleguen varios errores seguidos. */
|
|
127
|
+
_resume () {
|
|
128
|
+
if (this._resuming) return this._resuming
|
|
129
|
+
this.key = null
|
|
130
|
+
this._resuming = this._handshake()
|
|
131
|
+
.then(() => { this._emit('resumed', { sid: this.sid }) })
|
|
132
|
+
.catch((e) => { this._emit('error', e) })
|
|
133
|
+
.finally(() => { this._resuming = null })
|
|
134
|
+
return this._resuming
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** El saludo: abre una sesión con el agente y deja `sid` y `key`. */
|
|
138
|
+
async _handshake () {
|
|
108
139
|
const eph = await makeEphemeral()
|
|
109
140
|
// El self-cert P←P del modo self puede vencerse (24 h): refrescarlo si hace falta.
|
|
110
141
|
let cert = this.link.cert
|
|
@@ -151,11 +182,12 @@ export class RemoteAgentClient {
|
|
|
151
182
|
|
|
152
183
|
this.sid = res.sid
|
|
153
184
|
this.key = await deriveKey(eph.privateKey, res.ack.seph, res.sid)
|
|
154
|
-
return this
|
|
155
185
|
}
|
|
156
186
|
|
|
157
|
-
/** Envía un payload de dominio cifrado al agente. */
|
|
187
|
+
/** Envía un payload de dominio cifrado al agente (si se está volviendo a saludar, después). */
|
|
158
188
|
async send (payload) {
|
|
189
|
+
if (this._resuming) await this._resuming
|
|
190
|
+
if (!this.key) throw Object.assign(new Error('no session with the agent'), { code: 'no-session' })
|
|
159
191
|
const env = await seal(this.key, payload)
|
|
160
192
|
this.client.sendByPubkey(this.agentPubkey, { type: DATA, sid: this.sid, env })
|
|
161
193
|
}
|