panal-mcp 0.5.0 → 0.6.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.
@@ -52,8 +52,21 @@ export declare function pushBrief(botUrl: string, taskId: bigint, brief: string,
52
52
  * «no hay tope» es volver a averiguarlo pagando.
53
53
  */
54
54
  export declare function parseMaxBriefChars(card: unknown): number | null;
55
- export declare function fetchAgentLimits(botUrl: string): Promise<{
55
+ export interface LimitesDelAgente {
56
+ /**
57
+ * Si contestó ALGO, aunque fuera un error.
58
+ *
59
+ * Un 404 en `/agent.json` cuenta como alcanzable: hay agentes que no sirven
60
+ * tarjeta y su `POST /brief` funciona igual. Lo que descalifica es que no
61
+ * conteste nadie —DNS que no resuelve, conexión rechazada, timeout— o que la
62
+ * URL no pase el guard, porque entonces el encargo tampoco va a llegar.
63
+ *
64
+ * `null` = no se preguntó (el agente no publica endpoint, que es un modo
65
+ * soportado: el encargo se le hace llegar por otro canal).
66
+ */
67
+ alcanzable: boolean | null;
56
68
  maxBriefChars: number | null;
57
- }>;
69
+ }
70
+ export declare function fetchAgentLimits(botUrl: string): Promise<LimitesDelAgente>;
58
71
  /** Descarga acotada: se corta en cuanto se pasa del tope. */
59
72
  export declare function fetchResultText(botUrl: string, taskId: bigint, client: string, signature: string, expira: number): Promise<string>;
@@ -110,27 +110,35 @@ export function parseMaxBriefChars(card) {
110
110
  return typeof max === 'number' && Number.isInteger(max) && max > 0 ? max : null;
111
111
  }
112
112
  export async function fetchAgentLimits(botUrl) {
113
- const desconocido = { maxBriefChars: null };
114
113
  let url;
115
114
  try {
116
115
  const base = await assertPublicUrl(botUrl);
117
116
  url = new URL('/agent.json', base);
118
117
  }
119
118
  catch {
120
- return desconocido;
119
+ // El guard rechaza la URL: a esa dirección no se le puede entregar nada, ni
120
+ // ahora ni al contratar. Es inalcanzable, no «no lo sé».
121
+ return { alcanzable: false, maxBriefChars: null };
121
122
  }
122
123
  const controller = new AbortController();
123
124
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
124
125
  try {
125
126
  const res = await fetch(url, { signal: controller.signal, redirect: 'error' });
127
+ // A partir de aquí YA contestó, y eso no se revoca: una tarjeta ilegible o
128
+ // un 500 dejan el tope sin saber, pero el agente sigue siendo contratable.
129
+ // Meter el parseo en el catch de la red convertiría un JSON roto en «este
130
+ // agente no existe», que es una acusación distinta y falsa.
126
131
  if (!res.ok)
127
- return desconocido;
128
- return { maxBriefChars: parseMaxBriefChars(await res.json()) };
132
+ return { alcanzable: true, maxBriefChars: null };
133
+ try {
134
+ return { alcanzable: true, maxBriefChars: parseMaxBriefChars(await res.json()) };
135
+ }
136
+ catch {
137
+ return { alcanzable: true, maxBriefChars: null };
138
+ }
129
139
  }
130
140
  catch {
131
- // Un agente sin tarjeta, o que tarda, no impide contratarlo: solo deja el
132
- // límite sin comprobar. Fallar aquí seria peor que el problema.
133
- return desconocido;
141
+ return { alcanzable: false, maxBriefChars: null };
134
142
  }
135
143
  finally {
136
144
  clearTimeout(timer);
package/dist/server.js CHANGED
@@ -125,10 +125,21 @@ function trustLine(agent) {
125
125
  partes.push('DOMAIN NOT VERIFIED — the name alone proves nothing about who this is');
126
126
  else
127
127
  partes.push('domain NOT CHECKED on this listing — it was read from the chain, which does not record verification; the name alone proves nothing about who this is');
128
- if (agent.nombre && agent.nombre.origen !== 'reclamado') {
129
- const dias = Math.round(daysSince(agent.nombre.desdeTs));
130
- if (dias <= 30) {
131
- partes.push(`its name "${agent.nombre.nombre}" changed hands ${dias}d ago — reputation stays with the previous owner`);
128
+ // El nombre unico se dice SIEMPRE que lo haya. Es la unica prueba de
129
+ // identidad que vive en la cadena: el nombre del perfil es texto libre y se
130
+ // repite —hay tres direcciones anunciandose como "LexPanal"— mientras que
131
+ // este lo tiene una sola. Y a diferencia del dominio, no depende de que el
132
+ // indexador este vivo para poder afirmarlo.
133
+ if (agent.nombre) {
134
+ partes.push(`holds the unique name "${agent.nombre.nombre}" in PanalNames`);
135
+ // La advertencia solo con origen CONOCIDO y distinto de reclamado. Sin
136
+ // dato no se avisa: `undefined` seria «no lo se», y convertirlo en una
137
+ // acusacion de compra reciente es inventarse la mitad que importa.
138
+ if (agent.nombre.origen && agent.nombre.origen !== 'reclamado') {
139
+ const dias = Math.round(daysSince(agent.nombre.desdeTs));
140
+ if (dias <= 30) {
141
+ partes.push(`its name "${agent.nombre.nombre}" changed hands ${dias}d ago — reputation stays with the previous owner`);
142
+ }
132
143
  }
133
144
  }
134
145
  return ` Trust: ${partes.join('; ')}`;
@@ -394,7 +405,16 @@ const WRITE_TOOLS = [
394
405
  // tarea. Aquí no cuesta nada y evita dejar dinero atrapado hasta que
395
406
  // vence el plazo.
396
407
  if (agent.metadata.botUrl) {
397
- const { maxBriefChars } = await fetchAgentLimits(agent.metadata.botUrl);
408
+ const { alcanzable, maxBriefChars } = await fetchAgentLimits(agent.metadata.botUrl);
409
+ // Que el endpoint no conteste descalifica el encargo entero: el brief se
410
+ // entrega DESPUÉS de crear la tarea, así que contratar ahora dejaría el
411
+ // pago bloqueado en una tarea que nadie puede empezar. Aquí no cuesta
412
+ // nada y basta con reintentar cuando el agente vuelva.
413
+ if (alcanzable === false) {
414
+ return (`${agent.metadata.name || address} publishes ${agent.metadata.botUrl} but nothing answers there, ` +
415
+ `so the job could not be delivered even after paying. Nothing was quoted and nothing was spent. ` +
416
+ `Try again when the agent is back up.`);
417
+ }
398
418
  if (maxBriefChars !== null && brief.length > maxBriefChars) {
399
419
  return (`That job does not fit: ${agent.metadata.name || address} accepts ${maxBriefChars} characters ` +
400
420
  `and this brief is ${brief.length}. Nothing was quoted and nothing was spent. ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panal-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Servidor MCP de Panal: busca y contrata agentes de IA on-chain desde Claude",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "node": ">=20"
31
31
  },
32
32
  "dependencies": {
33
- "@panal/sdk": "^0.9.0",
33
+ "@panal/sdk": "^0.10.0",
34
34
  "viem": "^2.21.0"
35
35
  },
36
36
  "devDependencies": {