create-panal-agent 0.21.1 → 0.22.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
@@ -209,6 +209,21 @@ El escaparate habla diez idiomas; tu descripción, uno. `GET /agent.json?lang=fr
209
209
  propio modelo. Nadie tiene que aprender un formato nuevo: se siguen leyendo `description` y
210
210
  `tiers[].name`. Sin `LLM_API_KEY` no se cae nada — se sirve la ficha original.
211
211
 
212
+ ### Encargos de ejemplo
213
+
214
+ Quien te contrata ve, debajo de la caja del encargo, hasta **tres ejemplos de lo que te
215
+ puede pedir**, escritos por ti. Se ponen desde el panel de [panal.lat](https://panal.lat)
216
+ o desde la app, en «Editar ficha», y se guardan en tu ficha on-chain como
217
+ `ejemplo:<texto>`. No hay que tocar código: tu servidor los relee de la cadena cada cinco
218
+ minutos y los sirve en `/agent.json` (`examples`), traducidos como tu descripción.
219
+
220
+ Escribe lo que de verdad te piden, en una frase. Al pulsarlo, el cliente lo copia en la caja
221
+ y lo completa con lo suyo —el contrato, el texto, los datos—: un ejemplo no es un encargo
222
+ listo para pagar.
223
+
224
+ `npm run register` reescribe la ficha entera desde `PERFIL` y **borra** los niveles y los
225
+ ejemplos que pusiste desde el panel. Si ya estás registrado, edita la ficha desde el panel.
226
+
212
227
  ### Archivos, en las dos direcciones
213
228
 
214
229
  Tu agente **entrega** archivos devolviendo `{ text, files }` desde `handleTask`. El motor calcula el hash de cada uno y lo cuela en el texto antes de anclarlo, así que el cliente puede demostrar que lo que se baja es exactamente lo que entregaste. Un enlace a secas no daría eso.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-panal-agent",
3
- "version": "0.21.1",
3
+ "version": "0.22.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.19.0",
13
+ "@panal/sdk": "^0.20.0",
14
14
  "unpdf": "^1.8.1",
15
15
  "dotenv": "^17.0.0",
16
16
  "tsx": "^4.19.0",
@@ -31,6 +31,7 @@ import {
31
31
  assertCanServe,
32
32
  buildQuote,
33
33
  createPanalClient,
34
+ leerEjemplosDeMetadata,
34
35
  leerNiveles,
35
36
  leerNivelesDeMetadata,
36
37
  normalizarIdioma,
@@ -269,6 +270,15 @@ const NIVELES_DEL_CODIGO = NIVELES_OK;
269
270
  */
270
271
  let FICHA_TEXTO: { name: string; description: string } = { name: '', description: '' };
271
272
 
273
+ /**
274
+ * Los encargos de ejemplo que este agente propone al contratarle.
275
+ *
276
+ * Viven en la cadena (`ejemplo:` en la ficha) y se editan desde el panel de la
277
+ * web o desde la app, sin tocar este código. Se releen con los niveles, cada
278
+ * cinco minutos, y se sirven en `/agent.json` traducidos como la descripción.
279
+ */
280
+ let EJEMPLOS_OK: string[] = [];
281
+
272
282
  async function refrescarNiveles(): Promise<void> {
273
283
  try {
274
284
  const ficha = await panal.getAgent(account.address);
@@ -276,6 +286,7 @@ async function refrescarNiveles(): Promise<void> {
276
286
  name: ficha.metadata.name,
277
287
  description: ficha.metadata.description,
278
288
  };
289
+ EJEMPLOS_OK = leerEjemplosDeMetadata(ficha.metadataURI);
279
290
  const enCadena = leerNivelesDeMetadata(ficha.metadataURI);
280
291
  const antes = NIVELES_OK.map((n) => `${n.wei}:${n.name ?? ''}`).join('|');
281
292
  NIVELES_OK = enCadena.length > 0 ? enCadena : NIVELES_DEL_CODIGO;
@@ -1318,6 +1329,7 @@ const server = createServer((req, res) => {
1318
1329
  */
1319
1330
  const idioma = normalizarIdioma(url.searchParams.get('lang'));
1320
1331
  const nivelesFicha = NIVELES_OK.map(comoFicha);
1332
+ let ejemplos = EJEMPLOS_OK;
1321
1333
  let descripcion = FICHA_TEXTO.description;
1322
1334
  /**
1323
1335
  * En qué idioma va lo que se sirve, y `null` si va en el original.
@@ -1335,6 +1347,9 @@ const server = createServer((req, res) => {
1335
1347
  const frases = {
1336
1348
  description: descripcion,
1337
1349
  tiers: NIVELES_OK.map((n) => ({ name: n.name ?? '', description: n.description ?? '' })),
1350
+ // Sin campo si no hay: la huella de la ficha no cambia y lo que ya
1351
+ // estaba traducido sigue valiendo.
1352
+ ...(EJEMPLOS_OK.length > 0 ? { examples: EJEMPLOS_OK } : {}),
1338
1353
  };
1339
1354
  const traducido = frasesGuardadas(frases, idioma, DATA_DIR);
1340
1355
  if (!traducido) pedirTraduccion(frases, idioma, LLM_FICHA, DATA_DIR);
@@ -1349,6 +1364,7 @@ const server = createServer((req, res) => {
1349
1364
  if (destino.name && t.name) destino.name = t.name;
1350
1365
  if (destino.description && t.description) destino.description = t.description;
1351
1366
  });
1367
+ if (traducido.examples?.length === EJEMPLOS_OK.length) ejemplos = traducido.examples;
1352
1368
  }
1353
1369
  }
1354
1370
  const x402 =
@@ -1418,6 +1434,9 @@ const server = createServer((req, res) => {
1418
1434
  // Los niveles, sólo si este agente vende alguno. Ausente significa que
1419
1435
  // no los ofrece, y quien lee NO debe inventárselos a partir del precio.
1420
1436
  ...(nivelesFicha.length > 0 ? { tiers: nivelesFicha } : {}),
1437
+ // Los encargos de ejemplo, en el mismo idioma que la descripción. Los
1438
+ // escribe su dueño en la ficha de la cadena; ausente es que no hay.
1439
+ ...(ejemplos.length > 0 ? { examples: ejemplos } : {}),
1421
1440
  // ALIAS ANTIGUO, en la raíz. Aquí es donde esta plantilla lo publicaba
1422
1441
  // antes, y hay clientes ahí fuera que solo miran este sitio. Se sirve
1423
1442
  // por compatibilidad y desaparecerá; lo que se lee es `endpoints`.
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Aquí `GET /agent.json?lang=fr` devuelve tu MISMA ficha con las frases en
14
14
  * francés. Nadie tiene que aprender un formato nuevo: los lectores siguen
15
- * mirando `description` y `tiers[].name`, solo que traducidos.
15
+ * mirando `description`, `tiers[].name` y `examples`, solo que traducidos.
16
16
  *
17
17
  * QUÉ CUESTA, QUE ES LA PREGUNTA DE VERDAD
18
18
  *
@@ -65,6 +65,12 @@ export interface Frases {
65
65
  description: string;
66
66
  /** Nombre y descripción de cada nivel, en el orden en que van en la ficha. */
67
67
  tiers: { name: string; description: string }[];
68
+ /**
69
+ * Los encargos de ejemplo, en su orden. Sin campo cuando no hay ninguno, y
70
+ * a propósito: así la huella de una ficha sin ejemplos es la de siempre y
71
+ * sus traducciones ya guardadas siguen valiendo.
72
+ */
73
+ examples?: string[];
68
74
  }
69
75
 
70
76
  /** Tope de la respuesta que se acepta del modelo, por frase. */
@@ -144,7 +150,20 @@ function validar(v: unknown, original: Frases | null): Frases | null {
144
150
  if (typeof name !== 'string' || typeof d !== 'string') return null;
145
151
  salida.push({ name: name.trim().slice(0, MAX_FRASE), description: d.trim().slice(0, MAX_FRASE) });
146
152
  }
147
- return { description: description.trim().slice(0, MAX_FRASE), tiers: salida };
153
+ // Los ejemplos, con la misma regla que los niveles: los mismos en número o
154
+ // se descarta todo. Si el original no tiene, no se esperan.
155
+ const crudos = (v as Record<string, unknown>).examples;
156
+ let examples: string[] = [];
157
+ if ((original?.examples?.length ?? 0) > 0 || crudos !== undefined) {
158
+ if (!Array.isArray(crudos) || crudos.some((e) => typeof e !== 'string')) return null;
159
+ if (original && crudos.length !== (original.examples?.length ?? 0)) return null;
160
+ examples = (crudos as string[]).map((e) => e.trim().slice(0, MAX_FRASE));
161
+ }
162
+ return {
163
+ description: description.trim().slice(0, MAX_FRASE),
164
+ tiers: salida,
165
+ ...(examples.length > 0 ? { examples } : {}),
166
+ };
148
167
  }
149
168
 
150
169
  /** El JSON que venga, aunque llegue envuelto en un bloque de markdown. */