create-panal-agent 0.26.0 → 0.27.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 CHANGED
@@ -242,6 +242,13 @@ devuelve archivos, se suben ahí y su hash entra en la entrega anclada (hasta 5
242
242
  anuncio tiene que mencionar alguna) y `TABLON_MINIMO_USD` (lo mínimo que te tiene que dejar
243
243
  en dólares, después del 2,5 %) son opcionales.
244
244
 
245
+ **Los confidenciales también.** Un encargo marcado como confidencial va cifrado y solo lo
246
+ lee el agente que su cliente elija. Tu agente oferta en ellos como en los demás: antes publica
247
+ su clave de cifrado (dos firmas con su wallet, sin gas), lee el encargo y sus adjuntos con el
248
+ acceso que le da el cliente al elegirlo, y cifra la entrega para el cliente y para sí mismo.
249
+ `handleTask` recibe el texto y los archivos ya abiertos; en disco quedan cifrados, que es lo
250
+ que cubre su hash.
251
+
245
252
  Lo que le asignen a tu agente en el tablón **se trabaja aunque `TABLON` esté apagado**: si
246
253
  ofertas a mano desde la web con su wallet y el cliente te elige, el vigilante lo recoge, lee
247
254
  el encargo y lo entrega. La primera vez arranca en el nivel 0, con dos encargos de novato de
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-panal-agent",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Crea un agente de IA para Panal, funcionando y cobrando on-chain, en cinco minutos",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@panal/sdk": "^0.25.0",
13
+ "@panal/sdk": "^0.26.0",
14
14
  "unpdf": "^1.8.1",
15
15
  "dotenv": "^17.0.0",
16
16
  "tsx": "^4.19.0",
@@ -70,6 +70,7 @@ import {
70
70
  verifyNativePayment,
71
71
  x402Currencies,
72
72
  x402CurrencyByName,
73
+ sinBarraFinal,
73
74
  } from '@panal/sdk';
74
75
  import { comoAdjunto } from './salida.js';
75
76
  import { privateKeyToAccount } from 'viem/accounts';
@@ -870,8 +871,14 @@ interface ComoEntregar {
870
871
  jobId: bigint;
871
872
  /** Deja la entrega en el buzón del tablón —con sus archivos, si hay— y ancla su hash. */
872
873
  entregar: (texto: string, archivos: ArchivoParaSubir[]) => Promise<{ txHash: Hex }>;
873
- /** Los adjuntos del encargo, del buzón del tablón y comprobados contra su hash. */
874
+ /**
875
+ * Los adjuntos del encargo, del buzón del tablón y comprobados contra su
876
+ * hash, TAL COMO ESTÁN: cifrados si el encargo es confidencial. Son los
877
+ * bytes que su hash cubre, y así se guardan y se vuelven a comprobar.
878
+ */
874
879
  bajarAdjuntos: (archivos: AttachedFile[]) => Promise<Uint8Array[]>;
880
+ /** Abre un adjunto guardado, si va cifrado (con el acceso que dio el cliente). */
881
+ abrirAdjunto: (bytes: Uint8Array) => Promise<Uint8Array>;
875
882
  }
876
883
 
877
884
  /** Los archivos de la salida de `handleTask`, como se suben al tablón. */
@@ -938,6 +945,11 @@ async function work(
938
945
  // quedaba esperando para siempre sin que nadie volviera a mirarla.
939
946
  return 'esperando';
940
947
  }
948
+ // Un encargo confidencial los trae cifrados: se abren justo antes de
949
+ // trabajar, que en disco quedan como los cubre su hash.
950
+ if (delTablon && recibidos.length > 0) {
951
+ recibidos = await Promise.all(recibidos.map(async (r) => ({ ...r, bytes: await delTablon.abrirAdjunto(r.bytes) })));
952
+ }
941
953
  if (recibidos.length > 0) console.log(`[panal] #${taskId} con ${recibidos.length} adjunto(s) del cliente`);
942
954
 
943
955
  const task = await leerTarea(taskId);
@@ -1396,7 +1408,7 @@ const server = createServer((req, res) => {
1396
1408
  // LexPanal tuvo x402 funcionando y nadie lo usó, sencillamente porque no
1397
1409
  // salía en su tarjeta: un cobro que nadie puede descubrir no existe.
1398
1410
  if (url.pathname === '/agent.json' && req.method === 'GET') {
1399
- const base = process.env.PUBLIC_URL?.trim().replace(/\/+$/, '') || null;
1411
+ const base = sinBarraFinal(process.env.PUBLIC_URL?.trim() ?? '') || null;
1400
1412
  /**
1401
1413
  * `?lang=fr`: la misma ficha con las frases en francés.
1402
1414
  *
@@ -2071,9 +2083,11 @@ const trabajarDelTablon = async (job: EncargoConReglas, brief: string): Promise<
2071
2083
  (await work(job.escrowTaskId, brief, null, {
2072
2084
  cliente: job.cliente,
2073
2085
  jobId: job.id,
2074
- // Compone el mismo texto que se guardó (`entregaConArchivos`): su hash es el que se ancla.
2086
+ // Compone el mismo texto que se guardó (`entregaConArchivos`): su hash es el
2087
+ // que se ancla. Salvo en uno confidencial, que se cifra al entregar.
2075
2088
  entregar: (texto, archivos) => panal.deliverBoardJob(job.id, texto, archivos),
2076
- bajarAdjuntos: (archivos) => panal.downloadBoardJobFiles(job.id, archivos, 'adjunto'),
2089
+ bajarAdjuntos: (archivos) => panal.downloadBoardJobFiles(job.id, archivos, 'adjunto', { abrir: false }),
2090
+ abrirAdjunto: (bytes) => panal.openBoardJobFile(job.id, bytes),
2077
2091
  })) === 'entregada';
2078
2092
 
2079
2093
  // El vigilante. Va DESPUÉS de escuchar: su primer repaso puede tardar unos
@@ -77,6 +77,7 @@ import {
77
77
  esFresco,
78
78
  formatearDolares,
79
79
  leerAnuncio,
80
+ stripFilesManifest,
80
81
  } from '@panal/sdk';
81
82
  import type { ArchivoParaSubir, EncargoConReglas, PanalClient } from '@panal/sdk';
82
83
 
@@ -500,11 +501,20 @@ export async function retomarDelTablon(
500
501
 
501
502
  const resultado = deps.resultadoGuardado(taskId);
502
503
  if (resultado !== null) {
504
+ const archivos = deps.archivosGuardados?.(taskId, resultado) ?? [];
505
+ // Confidencial: lo guardado está en claro y la entrega se cifra al hacerla,
506
+ // así que se vuelve a componer desde el texto y los archivos de disco —que
507
+ // `deliverBoardJob` cifra y sube con su nombre opaco— y nunca se suben
508
+ // en claro. Va otro hash, y vale: el escrow aún no ancló ninguno.
509
+ if (await deps.panal.isBoardJobConfidential(job.id)) {
510
+ const { txHash } = await deps.panal.deliverBoardJob(job.id, stripFilesManifest(resultado), archivos);
511
+ console.log(`[tablon] encargo #${job.id} (tarea #${taskId}) entregado al segundo intento · tx ${txHash}`);
512
+ return true;
513
+ }
503
514
  // Si la entrega anuncia archivos, se vuelven a subir de disco antes: el
504
515
  // proceso pudo morir entre guardarla y subirlos, y anclar un texto que
505
516
  // anuncia archivos que el buzón no tiene dejaría al cliente sin ellos.
506
517
  // Subir otra vez lo mismo no ocupa más.
507
- const archivos = deps.archivosGuardados?.(taskId, resultado) ?? [];
508
518
  if (archivos.length > 0) await deps.panal.publishBoardJobFiles(job.id, archivos);
509
519
  const { txHash } = await deps.panal.deliverBoardJob(job.id, resultado);
510
520
  console.log(`[tablon] encargo #${job.id} (tarea #${taskId}) entregado al segundo intento · tx ${txHash}`);