create-panal-agent 0.8.0 → 0.9.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 +1 -1
- package/template/src/register.ts +7 -3
- package/template/src/server.ts +68 -3
package/package.json
CHANGED
package/template/src/register.ts
CHANGED
|
@@ -247,11 +247,15 @@ export function aHandle(nombre: string): string {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
|
-
* PanalNames en Monad mainnet
|
|
251
|
-
*
|
|
250
|
+
* PanalNames en Monad mainnet, desplegado el 2026-08-14 en el bloque 95750662.
|
|
251
|
+
*
|
|
252
|
+
* Es quien reparte los nombres unicos. Hoy reclamar no cuesta nada: la tarifa
|
|
253
|
+
* esta a cero para que un agente recien creado —que tiene MON para gas y cero
|
|
254
|
+
* $PANAL— pueda quedarse con el suyo desde el primer minuto.
|
|
255
|
+
*
|
|
252
256
|
* Se puede apuntar a otro con PANAL_NAMES_ADDRESS.
|
|
253
257
|
*/
|
|
254
|
-
const PANAL_NAMES = '';
|
|
258
|
+
const PANAL_NAMES = '0xc94a8107C87859cAd2E472e71BbE25c15cdD614A';
|
|
255
259
|
|
|
256
260
|
const NOMBRES_ABI = [
|
|
257
261
|
{
|
package/template/src/server.ts
CHANGED
|
@@ -421,7 +421,7 @@ async function work(taskId: bigint, brief: string, sobre: CallEnvelope | null):
|
|
|
421
421
|
// Lo PRIMERO, antes de trabajar: si el proceso muere a mitad, esto es lo
|
|
422
422
|
// único que permite retomarlo. Guardarlo después sería guardarlo nunca.
|
|
423
423
|
saveBrief(taskId, brief);
|
|
424
|
-
const task = await
|
|
424
|
+
const task = await leerTarea(taskId);
|
|
425
425
|
const salida = await handleTask(
|
|
426
426
|
brief,
|
|
427
427
|
contexto({ taskId, client: task.client, amount: task.amount, deadline: task.deadline }, sobre),
|
|
@@ -608,12 +608,65 @@ function pasaDelLimite(req: IncomingMessage): boolean {
|
|
|
608
608
|
const CACHE_TAREA_MS = 5_000;
|
|
609
609
|
const tareasCache = new Map<string, { cliente: Address; hasta: number }>();
|
|
610
610
|
|
|
611
|
+
/**
|
|
612
|
+
* La tarea existe en la cadena, pero el nodo que consultamos aún no la ve.
|
|
613
|
+
*
|
|
614
|
+
* NO es un error del agente ni del cliente, y por eso tiene su propio tipo:
|
|
615
|
+
* quien llama necesita poder distinguir «todavía no» de «se rompió algo», que
|
|
616
|
+
* son dos cosas con reacciones opuestas —una se reintenta, la otra no.
|
|
617
|
+
*/
|
|
618
|
+
class TareaAunNoVisible extends Error {
|
|
619
|
+
constructor(readonly taskId: bigint) {
|
|
620
|
+
super(`la tarea #${taskId} todavía no es visible en este nodo RPC`);
|
|
621
|
+
this.name = 'TareaAunNoVisible';
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* ¿Este fallo es «esa tarea no existe (todavía)»?
|
|
627
|
+
*
|
|
628
|
+
* `tasks` es un array público, así que su getter solo puede revertir por
|
|
629
|
+
* índice fuera de rango. Cualquier otro fallo —RPC caído, timeout, red— tiene
|
|
630
|
+
* otra forma y NO se disfraza de esto: tragárselo escondería una avería real.
|
|
631
|
+
*/
|
|
632
|
+
function pareceInexistente(err: unknown): boolean {
|
|
633
|
+
const m = err instanceof Error ? `${err.message}` : String(err);
|
|
634
|
+
return /revert|out-of-bounds|out of bounds|0x32/i.test(m);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Lee la tarea aguantando el desfase entre nodos.
|
|
639
|
+
*
|
|
640
|
+
* POR QUÉ EXISTE. El cliente mina `createTask` contra SU RPC y, en cuanto
|
|
641
|
+
* tiene el recibo, nos manda el encargo. Nosotros validamos leyendo la tarea
|
|
642
|
+
* contra el NUESTRO, que es otro nodo y puede ir un bloque por detrás: para él
|
|
643
|
+
* esa tarea aún no existe, el getter revierte y el envío se caía con un 500.
|
|
644
|
+
* El cliente veía «no se pudo enviar el brief» y tenía que reintentar a mano,
|
|
645
|
+
* con su dinero ya bloqueado. Fallaba a la primera y funcionaba a la segunda,
|
|
646
|
+
* que es la firma de una carrera, no de una avería.
|
|
647
|
+
*
|
|
648
|
+
* Cuatro intentos con espera creciente cubren de sobra un bloque de Monad
|
|
649
|
+
* (~800 ms) sin castigar al RPC compartido.
|
|
650
|
+
*/
|
|
651
|
+
async function leerTarea(taskId: bigint): ReturnType<typeof panal.getTask> {
|
|
652
|
+
for (let intento = 1; intento <= 4; intento++) {
|
|
653
|
+
try {
|
|
654
|
+
return await panal.getTask(taskId);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
if (!pareceInexistente(err)) throw err;
|
|
657
|
+
if (intento === 4) break;
|
|
658
|
+
await new Promise((r) => setTimeout(r, 250 * intento));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
throw new TareaAunNoVisible(taskId);
|
|
662
|
+
}
|
|
663
|
+
|
|
611
664
|
async function clienteDeTarea(taskId: bigint): Promise<Address> {
|
|
612
665
|
const k = taskId.toString();
|
|
613
666
|
const ahora = Date.now();
|
|
614
667
|
const cacheada = tareasCache.get(k);
|
|
615
668
|
if (cacheada && cacheada.hasta > ahora) return cacheada.cliente;
|
|
616
|
-
const task = await
|
|
669
|
+
const task = await leerTarea(taskId);
|
|
617
670
|
if (tareasCache.size > 1_000) for (const [kk, v] of tareasCache) if (v.hasta <= ahora) tareasCache.delete(kk);
|
|
618
671
|
tareasCache.set(k, { cliente: task.client, hasta: ahora + CACHE_TAREA_MS });
|
|
619
672
|
return task.client;
|
|
@@ -829,7 +882,9 @@ const server = createServer((req, res) => {
|
|
|
829
882
|
throw err;
|
|
830
883
|
}
|
|
831
884
|
|
|
832
|
-
|
|
885
|
+
// Con reintentos: el cliente acaba de minar la tarea contra otro nodo y
|
|
886
|
+
// el nuestro puede ir por detrás. Ver `leerTarea`.
|
|
887
|
+
const task = await leerTarea(taskId);
|
|
833
888
|
|
|
834
889
|
// Cuatro comprobaciones, y las cuatro importan: que la tarea sea tuya,
|
|
835
890
|
// que siga abierta, que quien dice firmar sea el cliente que pagó, y que
|
|
@@ -956,6 +1011,16 @@ const server = createServer((req, res) => {
|
|
|
956
1011
|
|
|
957
1012
|
json(res, 404, { error: 'no existe' });
|
|
958
1013
|
})().catch((err) => {
|
|
1014
|
+
// «Todavía no la veo» NO es un 500. Con 425 (Too Early) quien llama sabe
|
|
1015
|
+
// que reintentar tiene sentido; con 500 parecía una avería del agente y el
|
|
1016
|
+
// dashboard se rendía dejando al cliente reenviando a mano. Se responde
|
|
1017
|
+
// rápido y sin ruido en el log: no hay nada roto que mirar.
|
|
1018
|
+
if (err instanceof TareaAunNoVisible) {
|
|
1019
|
+
if (!res.headersSent) {
|
|
1020
|
+
json(res, 425, { error: err.message, reintentable: true });
|
|
1021
|
+
} else res.end();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
959
1024
|
console.error(`[http] ${err instanceof Error ? err.message : err}`);
|
|
960
1025
|
if (!res.headersSent) json(res, 500, { error: 'error interno' });
|
|
961
1026
|
else res.end();
|