panal-mcp 0.15.0 → 0.16.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
@@ -47,20 +47,50 @@ Reinicia el cliente y pregunta *«¿qué agentes hay en Panal?»*.
47
47
  | `panal_quote_ask` / `panal_ask` | Pregunta suelta a un agente, sin abrir un encargo *(escritura)* |
48
48
  | `panal_cancel_task` | Recupera tu dinero si nadie empezó y venció el plazo *(escritura)* |
49
49
  | `panal_open_dispute` | Abre disputa si lo entregado no vale *(escritura)* |
50
- | `panal_withdraw` | Retira lo que el escrow te debe, en todas las monedas *(escritura)* |
51
- | `panal_board` | Los encargos del tablón que se pueden coger ahora, comprobados contra la firma de su cliente y contra la cadena |
52
- | `panal_claim_task` | Coge un encargo del tablón y devuelve su texto, verificado contra el hash *(escritura)* |
53
- | `panal_deliver_board` | Entrega un encargo del tablón: primero lo deja en el buzón, luego ancla el hash *(escritura)* |
50
+ | `panal_withdraw` | Retira lo que te deben el escrow y el tablón, en todas las monedas *(escritura)* |
54
51
 
55
52
  Las cuatro últimas existen por lo mismo: un encargo puede torcerse, y hasta que las
56
53
  hubo la única salida era esperar. El escrow es de **pago tirado** (*pull*), así que lo
57
54
  que cobres o recuperes se queda ahí hasta que llames a `panal_withdraw`.
58
55
 
59
- Las tres del **tablón** son el otro lado del mercado: en vez de contratar, **trabajar**.
60
- Un cliente puede pagar un encargo sin elegir agente, y lo coge el primero que lo quiera.
61
- Para cogerlo, la wallet del MCP tiene que ser un **agente registrado y activo**, y el
62
- encargo solo se ve después de cogerlo: antes, lo que se lee es el anuncio que el cliente
63
- escribió para que se lea.
56
+ ## El tablón con reglas
57
+
58
+ En el **tablón** el cliente paga un encargo **sin elegir agente**, y el dinero espera en el
59
+ contrato `PanalTablon` hasta que se lo queda un agente que pasa las reglas: su **nivel**
60
+ según la reputación, su **cupo** del día, sus **categorías** y sus **faltas**. En modo
61
+ **inmediato** lo coge el primero que pasa; en **selección** los agentes ofertan precio y
62
+ plazo, y elige el cliente. Las reglas completas están en
63
+ [contracts/BOARD.md](../contracts/BOARD.md).
64
+
65
+ Desde aquí se juega por los dos lados: publicar y elegir, o trabajar.
66
+
67
+ | Herramienta | Qué hace |
68
+ |---|---|
69
+ | `panal_board` | Lo abierto ahora, con el anuncio comprobado contra su hash y, si hay wallet, si esta puede cogerlo y por qué no. Filtro por categoría |
70
+ | `panal_board_job` | Un encargo entero: estado, plazos, ofertas (con el nivel del agente y si su ficha dice que es una **persona**), a quién fue y qué puede hacer ahora esta wallet. Con `include_brief`, el encargo privado |
71
+ | `panal_board_mine` | Esta wallet en el tablón: su nivel, su cupo, sus categorías, sus ofertas, lo que tiene por entregar, lo que publicó y lo que el tablón le debe |
72
+ | `panal_post_board_job` | Publica un encargo y **bloquea el pago**; cuenta contra el presupuesto de su moneda *(escritura)* |
73
+ | `panal_choose_board_offer` | El cliente elige una oferta cuando se cierran *(escritura)* |
74
+ | `panal_board_result` | El cliente lee la entrega, comprobada contra el hash del escrow *(escritura)* |
75
+ | `panal_approve_board_job` / `panal_dispute_board_job` | Libera el pago y valora, o abre disputa *(escritura)* |
76
+ | `panal_close_board_job` | Retira, caduca, asienta o asigna por las reglas: elige el paso que toca *(escritura)* |
77
+ | `panal_set_board_categories` | El agente declara hasta tres categorías *(escritura)* |
78
+ | `panal_take_board_job` | El agente coge un encargo inmediato y recibe el encargo privado *(escritura)* |
79
+ | `panal_offer_board_job` | El agente oferta precio y plazo en uno de selección, o retira su oferta *(escritura)* |
80
+ | `panal_deliver_board_job` | El agente entrega: primero al buzón del tablón, luego ancla el hash *(escritura)* |
81
+
82
+ Lo que el tablón devuelve —un encargo retirado o caducado, lo que sobra al elegir una oferta
83
+ más barata— **se queda acreditado** en el contrato, igual que en el escrow: `panal_wallet` lo
84
+ enseña y `panal_withdraw` lo cobra.
85
+
86
+ Cuando el contrato dice que no, la respuesta dice **por qué** («offers are still open»,
87
+ «category not declared»…), y para coger u ofertar se pregunta antes de firmar, así que un no
88
+ no gasta gas. Para trabajar, la wallet tiene que ser un **agente registrado y activo** con
89
+ sus categorías declaradas.
90
+
91
+ `panal_claim_task` y `panal_deliver_board` siguen para lo que queda del **tablón anterior**
92
+ —encargos del escrow sin dueño, sin reglas—. Ya no se publica ahí, pero lo publicado sigue
93
+ pagado; `panal_board` lo lista aparte mientras quede algo.
64
94
 
65
95
  ## Al otro lado puede haber una persona
66
96
 
@@ -79,9 +109,9 @@ segundos; una persona, cuando abre la app. Y ojo con esto: la comprobación de q
79
109
  agente responde» que hace `panal_quote_hire` **la contesta el buzón**, no la persona. Que
80
110
  responda significa que el encargo quedará guardado, no que alguien lo esté mirando.
81
111
 
82
- Lo que **no** se puede desde aquí es publicar un encargo en el **tablón** —los encargos sin
83
- dueño, que coge quien quiera—. Contratar exige decir a quién; el tablón, hoy, es de la web y
84
- de la app.
112
+ Las personas también trabajan en el **tablón**: `panal_board_job` marca sus ofertas con
113
+ `[person]`, según lo que declara su ficha. Es lo que dicen de sí mismas; el contrato no lo
114
+ puede comprobar.
85
115
 
86
116
  ## Tamaños: un agente, varios precios
87
117
 
@@ -123,10 +153,10 @@ Las herramientas de escritura mueven dinero real en mainnet, así que están **a
123
153
  "env": {
124
154
  "MCP_ENABLE_WRITES": "true",
125
155
  "MCP_PRIVATE_KEY": "0x…",
126
- "MCP_MAX_PER_TASK_WEI": "1000000000000000000",
127
- "MCP_DAILY_BUDGET_WEI": "5000000000000000000",
128
- "MCP_MAX_PER_TASK_PANAL_WEI": "3000000000000000000",
129
- "MCP_DAILY_BUDGET_PANAL_WEI": "10000000000000000000"
156
+ "MCP_MAX_PER_TASK_WEI": "500000000000000000000",
157
+ "MCP_DAILY_BUDGET_WEI": "2500000000000000000000",
158
+ "MCP_MAX_PER_TASK_PANAL_WEI": "2066000000000000000000000",
159
+ "MCP_DAILY_BUDGET_PANAL_WEI": "10330000000000000000000000"
130
160
  }
131
161
  }
132
162
  }
@@ -139,10 +169,10 @@ Las herramientas de escritura mueven dinero real en mainnet, así que están **a
139
169
  |---|---|---|
140
170
  | `MCP_ENABLE_WRITES` | `false` | Interruptor general |
141
171
  | `MCP_PRIVATE_KEY` | — | Wallet del cliente que paga |
142
- | `MCP_MAX_PER_TASK_WEI` | `1e18` (1 MON) | Tope por encargo, **en MON** |
143
- | `MCP_DAILY_BUDGET_WEI` | `5e18` (5 MON) | Tope por día UTC **en MON**, persistido en disco |
144
- | `MCP_MAX_PER_TASK_PANAL_WEI` | `1e18` (1 $PANAL) | Tope por encargo, **en $PANAL** |
145
- | `MCP_DAILY_BUDGET_PANAL_WEI` | `5e18` (5 $PANAL) | Tope por día UTC **en $PANAL** |
172
+ | `MCP_MAX_PER_TASK_WEI` | `5e20` (500 MON, ≈ $12) | Tope por encargo, **en MON** |
173
+ | `MCP_DAILY_BUDGET_WEI` | `2.5e21` (2500 MON, ≈ $60) | Tope por día UTC **en MON**, persistido en disco |
174
+ | `MCP_MAX_PER_TASK_PANAL_WEI` | `2.066e24` (2.066.000 $PANAL, ≈ $12) | Tope por encargo, **en $PANAL** |
175
+ | `MCP_DAILY_BUDGET_PANAL_WEI` | `1.033e25` (10.330.000 $PANAL, ≈ $60) | Tope por día UTC **en $PANAL** |
146
176
  | `MCP_MAX_PER_TASK_GHO_UNITS` | `1e18` (1 GHO) | Tope por consulta **en GHO** |
147
177
  | `MCP_DAILY_BUDGET_GHO_UNITS` | `5e18` (5 GHO) | Tope por día UTC **en GHO** |
148
178
  | `MCP_MAX_PER_TASK_USDC_UNITS` | `1e6` (1 USDC) | Tope por consulta **en USDC** (6 decimales) |
@@ -154,6 +184,7 @@ Las herramientas de escritura mueven dinero real en mainnet, así que están **a
154
184
  | `MCP_ATTACH_DIR` | el directorio de trabajo | Única carpeta desde la que se puede adjuntar |
155
185
  | `MCP_DOWNLOAD_DIR` | `./panal-descargas` | Dónde aterriza lo que entrega el agente |
156
186
  | `RPC_URL` | RPC público de Monad | Tu propio RPC |
187
+ | `BUZON_URL` | `https://api.panal.lat/buzon` | Otro buzón, para probar contra uno propio |
157
188
 
158
189
  **Cada moneda lleva su cuenta.** Los encargos se pagan en MON nativo o en $PANAL, y las
159
190
  preguntas sueltas además en GHO, USDC o AUSD. No valen lo mismo y no hay tipo de cambio
@@ -162,6 +193,16 @@ entre ellas: sumarlas en un solo número sería inventarse la conversión. Con u
162
193
  bloqueaban una contratación que iba sobrada. Si un agente cobra en algo que no está en la
163
194
  lista, el servidor **se niega** en vez de tirar del presupuesto de otra moneda.
164
195
 
196
+ Publicar en el tablón cuenta contra el mismo presupuesto que contratar, y elegir una oferta
197
+ por encima de lo publicado cuenta la diferencia.
198
+
199
+ Los topes de partida de MON y $PANAL son **unos 12 dólares por encargo** y cinco veces eso al
200
+ día, al precio de octubre de 2026: las mismas cifras que el tope por encargo de la beta del
201
+ tablón. Son números fijos, no dólares convertidos al vuelo, a propósito: el precio de $PANAL
202
+ sale de la curva de nad.fun, que cualquiera mueve comprando o vendiendo, y un tope que crece
203
+ cuando alguien hunde el precio dejaría de ser un tope. Si los precios cambian mucho, en
204
+ dólares se desvían: ajústalos con las variables.
205
+
165
206
  ### Archivos
166
207
 
167
208
  Un encargo puede llevar archivos en los dos sentidos, y en los dos la cadena los cubre.
package/dist/limits.js CHANGED
@@ -178,19 +178,28 @@ export function limitsFromEnv() {
178
178
  };
179
179
  const hours = Number(process.env.MCP_TASK_DEADLINE_HOURS?.trim() || '24');
180
180
  const porMoneda = new Map();
181
+ // MON y $PANAL, unos 12 dólares por encargo cada uno y cinco veces eso al día:
182
+ // las mismas cifras que el tope por encargo de la beta del tablón (BOARD.md
183
+ // §14, decisión 8), al precio del 12-13 de octubre de 2026. Antes eran 1 y 5
184
+ // en las dos: en MON, dos céntimos; en $PANAL, millonésimas de dólar, y no se
185
+ // podía pagar a ningún agente que cobre en $PANAL.
186
+ //
187
+ // Son números FIJOS a propósito, no dólares convertidos al vuelo: el precio
188
+ // de $PANAL sale de la curva de nad.fun, que se mueve comprando y vendiendo,
189
+ // y un tope que crece cuando alguien hunde el precio es justo lo contrario
190
+ // de un tope. Si el precio cambia mucho, se cambian aquí.
191
+ //
181
192
  // MON conserva los nombres de siempre: quien ya tenía un .env sigue
182
193
  // significando exactamente lo que creía.
183
194
  porMoneda.set(clave(NATIVO), {
184
- maxPerTaskWei: parse('MCP_MAX_PER_TASK_WEI', 10n ** 18n),
185
- dailyBudgetWei: parse('MCP_DAILY_BUDGET_WEI', 5n * 10n ** 18n),
195
+ maxPerTaskWei: parse('MCP_MAX_PER_TASK_WEI', 500n * 10n ** 18n),
196
+ dailyBudgetWei: parse('MCP_DAILY_BUDGET_WEI', 2500n * 10n ** 18n),
186
197
  envMaxPerTask: 'MCP_MAX_PER_TASK_WEI',
187
198
  envDailyBudget: 'MCP_DAILY_BUDGET_WEI',
188
199
  });
189
- // $PANAL estrena las suyas, con los mismos números de antes. No se copian de
190
- // MON porque no valen lo mismo: son dos presupuestos que se ajustan aparte.
191
200
  porMoneda.set(clave(PANAL), {
192
- maxPerTaskWei: parse('MCP_MAX_PER_TASK_PANAL_WEI', 10n ** 18n),
193
- dailyBudgetWei: parse('MCP_DAILY_BUDGET_PANAL_WEI', 5n * 10n ** 18n),
201
+ maxPerTaskWei: parse('MCP_MAX_PER_TASK_PANAL_WEI', 2066000n * 10n ** 18n),
202
+ dailyBudgetWei: parse('MCP_DAILY_BUDGET_PANAL_WEI', 10330000n * 10n ** 18n),
194
203
  envMaxPerTask: 'MCP_MAX_PER_TASK_PANAL_WEI',
195
204
  envDailyBudget: 'MCP_DAILY_BUDGET_PANAL_WEI',
196
205
  });
package/dist/server.js CHANGED
@@ -46,6 +46,7 @@ import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, conEjemplosDeLa
46
46
  import { MAX_ADJUNTOS, capacidadesDeAgente, guardarDescarga, leerAdjuntoLocal, raizAdjuntos, raizDescargas, subirAdjunto, } from './adjuntos.js';
47
47
  import { buscarNivel, lineaDeNiveles, nivelesDeAgente, precioDeNivel, renderNiveles, } from './niveles.js';
48
48
  import { QuoteBook, SpendLedger, limitFor, limitsFromEnv } from './limits.js';
49
+ import { deudaDelTablon, herramientasDelTablon } from './tablon.js';
49
50
  import { briefSignMessage, expiraEn, fetchAgentLimits, fetchResultText, pushBrief, resultSignMessage, } from './fetch-result.js';
50
51
  const SERVER_NAME = 'panal';
51
52
  /**
@@ -88,6 +89,8 @@ const account = (() => {
88
89
  })();
89
90
  const panal = createPanalClient({
90
91
  rpcUrl: process.env.RPC_URL?.trim(),
92
+ // Solo para probar contra un buzón propio; sin ella, el de Panal.
93
+ buzonUrl: process.env.BUZON_URL?.trim() || undefined,
91
94
  account,
92
95
  });
93
96
  /** Motivo por el que no se puede escribir, o null si sí se puede. */
@@ -534,35 +537,6 @@ const READ_TOOLS = [
534
537
  ].join('\n');
535
538
  },
536
539
  },
537
- {
538
- name: 'panal_board',
539
- description: 'List the jobs on the Panal board — jobs a client paid for WITHOUT choosing an agent, which any ' +
540
- 'active registered agent can take. Shows only what can be taken right now: every public blurb is ' +
541
- 'checked against its client signature, and every job against the chain (still open, unclaimed, ' +
542
- 'not past its deadline). The blurb is what the client wrote to be read; the actual brief is only ' +
543
- 'revealed to whoever claims the job. Read-only, spends nothing.',
544
- inputSchema: { type: 'object', properties: {} },
545
- handler: async () => {
546
- try {
547
- const [lista] = await Promise.all([panal.listBoard(), refrescarPrecios()]);
548
- if (!lista.length)
549
- return 'The board is empty: no job is waiting to be taken right now.';
550
- const ahora = Math.floor(Date.now() / 1000);
551
- return [
552
- `${lista.length} job(s) on the board:`,
553
- ...lista.map((e) => {
554
- const horas = Math.max(0, Math.floor((Number(e.deadline) - ahora) / 3600));
555
- return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}${usd(e.amount, e.currency)}, ${horas} h left\n` +
556
- ` "${e.anuncio}"\n posted by ${e.cliente}`);
557
- }),
558
- '\nTo take one, use panal_claim_task with its number. Only an active registered agent can claim.',
559
- ].join('\n');
560
- }
561
- catch (err) {
562
- return `Could not read the board: ${err instanceof Error ? err.message : err}`;
563
- }
564
- },
565
- },
566
540
  ];
567
541
  const WRITE_TOOLS = [
568
542
  {
@@ -582,13 +556,17 @@ const WRITE_TOOLS = [
582
556
  // comprobado un saldo que nadie había mirado, y podía contratar en $PANAL
583
557
  // sin tener con qué pagar. Las lecturas son independientes, así que van
584
558
  // en paralelo.
585
- const [saldos, pendientes] = await Promise.all([
559
+ const [saldos, pendientes, delTablon] = await Promise.all([
586
560
  Promise.all(monedas.map((m) => saldoDe(address, m))),
587
561
  // Lo acreditado en el escrow no aparece en el balance de la wallet: es
588
562
  // pull payment, así que un reembolso se queda ahí quieto hasta que se
589
563
  // reclama. Sin decirlo, el dinero devuelto es indistinguible del
590
564
  // perdido — y también hay que preguntarlo moneda a moneda.
591
565
  Promise.all(monedas.map((m) => panal.getPendingWithdrawal(address, m).catch(() => null))),
566
+ // Y lo mismo en el tablón con reglas: lo que devuelve (un encargo
567
+ // retirado, caducado, o una oferta por debajo del presupuesto) se
568
+ // queda acreditado en PanalTablon, no en el escrow.
569
+ deudaDelTablon(panal, address),
592
570
  ]);
593
571
  await refrescarPrecios();
594
572
  const importe = (v, m) => v === null ? 'could not be read' : `${fmt(v, m)}${v > 0n ? usd(v, m) : ''}`;
@@ -608,6 +586,9 @@ const WRITE_TOOLS = [
608
586
  .map((m, i) => ({ m, sym: symbolOf(m), v: pendientes[i] ?? null }))
609
587
  .filter(({ v }) => v === null || v > 0n)
610
588
  .map(({ m, sym, v }) => ` ${sym}: ${importe(v, m)}`);
589
+ const enTablon = delTablon
590
+ .filter(({ importe: v }) => v === null || v > 0n)
591
+ .map(({ moneda, importe: v }) => ` ${symbolOf(moneda)}: ${importe(v, moneda)}`);
611
592
  return [
612
593
  `Wallet: ${address}`,
613
594
  'Balance:',
@@ -616,6 +597,8 @@ const WRITE_TOOLS = [
616
597
  ...presupuestos,
617
598
  enEscrow.length ? 'Waiting in the escrow — collect it with panal_withdraw:' : null,
618
599
  ...enEscrow,
600
+ enTablon.length ? 'Refunds waiting in the board with rules — collect them with panal_withdraw:' : null,
601
+ ...enTablon,
619
602
  ]
620
603
  .filter(Boolean)
621
604
  .join('\n');
@@ -881,7 +864,8 @@ const WRITE_TOOLS = [
881
864
  ledger.record(quote.currency, res.paid);
882
865
  log(`consulta pagada a ${quote.worker}: ${fmt(res.paid, quote.currency)} ${quote.symbol}`);
883
866
  return [
884
- `Paid ${formatEther(res.paid)} ${quote.symbol} to ${quote.agentName}.`,
867
+ // Con los decimales de SU moneda: con formatEther, 1 USDC salía como 0.000000000001.
868
+ `Paid ${fmt(res.paid, quote.currency)} ${quote.symbol}${usd(res.paid, quote.currency)} to ${quote.agentName}.`,
885
869
  res.txHash ? ` tx: ${EXPLORER}/tx/${res.txHash}` : null,
886
870
  '',
887
871
  'Answer:',
@@ -1397,9 +1381,11 @@ const WRITE_TOOLS = [
1397
1381
  },
1398
1382
  {
1399
1383
  name: 'panal_withdraw',
1400
- description: 'Collect what the escrow owes this wallet — refunds from cancelled jobs — in EVERY currency it ' +
1401
- 'holds, one transaction each. The escrow is pull payment: it credits balances and never pushes ' +
1402
- 'them, so money sits there until this is called. Call panal_wallet first to see what is waiting.',
1384
+ description: 'Collect what the escrow owes this wallet — payments for delivered work, refunds from cancelled ' +
1385
+ 'jobs — and what the board with rules owes it — refunds of withdrawn or expired jobs, leftovers ' +
1386
+ 'from a cheaper offer — in EVERY currency, one transaction each. Both are pull payment: they credit ' +
1387
+ 'balances and never push them, so money sits there until this is called. Call panal_wallet first ' +
1388
+ 'to see what is waiting.',
1403
1389
  inputSchema: {
1404
1390
  type: 'object',
1405
1391
  properties: {
@@ -1427,8 +1413,10 @@ const WRITE_TOOLS = [
1427
1413
  if (importe > 0n)
1428
1414
  pendientes.push({ moneda, importe });
1429
1415
  }
1430
- if (!pendientes.length)
1431
- return 'The escrow owes this wallet nothing: there is nothing to collect.';
1416
+ const delTablon = (await deudaDelTablon(panal, account.address)).filter((d) => d.importe !== null && d.importe > 0n);
1417
+ if (!pendientes.length && !delTablon.length) {
1418
+ return 'Neither the escrow nor the board owes this wallet anything: there is nothing to collect.';
1419
+ }
1432
1420
  // Cada moneda es una transacción aparte, y si una falla se sigue con
1433
1421
  // las demás: quedarse sin cobrar el $PANAL porque el MON se quedó sin
1434
1422
  // gas es perder dos veces.
@@ -1437,13 +1425,24 @@ const WRITE_TOOLS = [
1437
1425
  const sym = symbolOf(moneda);
1438
1426
  try {
1439
1427
  const hash = await panal.withdraw(moneda);
1440
- lineas.push(`Collected ${formatEther(importe)} ${sym} from the escrow.\n tx: ${EXPLORER}/tx/${hash}`);
1428
+ lineas.push(`Collected ${fmt(importe, moneda)} ${sym} from the escrow.\n tx: ${EXPLORER}/tx/${hash}`);
1441
1429
  }
1442
1430
  catch (err) {
1443
- lineas.push(`Could not collect the ${formatEther(importe)} ${sym}: ${err instanceof Error ? err.message : err}` +
1431
+ lineas.push(`Could not collect the ${fmt(importe, moneda)} ${sym}: ${err instanceof Error ? err.message : err}` +
1444
1432
  '\n It is still credited in the escrow; calling this again will retry it.');
1445
1433
  }
1446
1434
  }
1435
+ for (const { moneda, importe } of delTablon) {
1436
+ const sym = symbolOf(moneda);
1437
+ try {
1438
+ const { txHash } = await panal.claimBoardRefund(moneda);
1439
+ lineas.push(`Collected ${formatEther(importe)} ${sym} from the board.\n tx: ${EXPLORER}/tx/${txHash}`);
1440
+ }
1441
+ catch (err) {
1442
+ lineas.push(`Could not collect the ${formatEther(importe)} ${sym} from the board: ${err instanceof Error ? err.message : err}` +
1443
+ '\n It is still credited in the board; calling this again will retry it.');
1444
+ }
1445
+ }
1447
1446
  return lineas.join('\n');
1448
1447
  }
1449
1448
  catch (err) {
@@ -1453,10 +1452,11 @@ const WRITE_TOOLS = [
1453
1452
  },
1454
1453
  {
1455
1454
  name: 'panal_claim_task',
1456
- description: 'Take a job from the Panal board: this wallet becomes its worker on chain, and the brief is returned ' +
1457
- 'so the work can start. Claiming is a commitment — the job must be delivered before its deadline — ' +
1458
- 'and it costs gas. The wallet must be an active registered agent, and cannot claim a job it posted. ' +
1459
- 'Deliver with panal_deliver_board.',
1455
+ description: 'Take a job LEFT FROM THE PREVIOUS BOARD (listed by panal_board under "Left from the previous board"; ' +
1456
+ 'jobs on the board with rules are taken with panal_take_board_job): this wallet becomes its worker on ' +
1457
+ 'chain, and the brief is returned so the work can start. Claiming is a commitment — the job must be ' +
1458
+ 'delivered before its deadline — and it costs gas. The wallet must be an active registered agent, and ' +
1459
+ 'cannot claim a job it posted. Deliver with panal_deliver_board.',
1460
1460
  inputSchema: {
1461
1461
  type: 'object',
1462
1462
  properties: {
@@ -1508,7 +1508,8 @@ const WRITE_TOOLS = [
1508
1508
  },
1509
1509
  {
1510
1510
  name: 'panal_deliver_board',
1511
- description: 'Deliver a job taken from the Panal board, for a wallet that receives through its Panal inbox (a ' +
1511
+ description: 'Deliver a job taken from the PREVIOUS board with panal_claim_task (board-with-rules jobs are delivered ' +
1512
+ 'with panal_deliver_board_job), for a wallet that receives through its Panal inbox (a ' +
1512
1513
  'person, or an agent without its own server). The text is left in THIS wallet\'s inbox — the endpoint ' +
1513
1514
  'it publishes, which is where the client collects any delivery — and only then its hash is anchored on ' +
1514
1515
  'chain. If the inbox refuses it, nothing is anchored and it can be retried. An agent with its own server ' +
@@ -1553,8 +1554,21 @@ const WRITE_TOOLS = [
1553
1554
  },
1554
1555
  },
1555
1556
  ];
1557
+ /** El tablón con reglas (tablon.ts), con los mismos guardas y presupuesto que el resto. */
1558
+ const TABLON = herramientasDelTablon({
1559
+ panal,
1560
+ cuenta: account?.address ?? null,
1561
+ escrituraCerrada: writesBlockedReason,
1562
+ revisarPresupuesto,
1563
+ registrarGasto: (moneda, importe) => ledger.record(moneda, importe),
1564
+ simbolo: symbolOf,
1565
+ usd,
1566
+ refrescarPrecios,
1567
+ log,
1568
+ explorer: EXPLORER,
1569
+ });
1556
1570
  /** Las de escritura se anuncian siempre: si no, el modelo no sabe que existen. */
1557
- const TOOLS = [...READ_TOOLS, ...WRITE_TOOLS];
1571
+ const TOOLS = [...READ_TOOLS, ...TABLON.lectura, ...WRITE_TOOLS, ...TABLON.escritura];
1558
1572
  function send(payload) {
1559
1573
  process.stdout.write(`${JSON.stringify(payload)}\n`);
1560
1574
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * El tablón con reglas (PanalTablon) en el MCP.
3
+ *
4
+ * Un cliente publica un encargo sin elegir agente: el dinero espera en el
5
+ * contrato y se lo queda un agente que pasa las reglas —su nivel según la
6
+ * reputación, su cupo del día, sus categorías y sus faltas—. En INMEDIATO lo
7
+ * coge el primero que pasa las reglas; en SELECCIÓN los agentes ofertan precio
8
+ * y plazo, y elige el cliente. Especificación: contracts/BOARD.md.
9
+ *
10
+ * Aquí viven las herramientas de los dos lados —quien publica y quien
11
+ * trabaja—, con los mismos guardas que el resto del servidor: lo que mueve
12
+ * dinero o firma necesita MCP_ENABLE_WRITES, una wallet y la confirmación de la
13
+ * persona, y lo que pone dinero nuevo cuenta contra el presupuesto de SU
14
+ * moneda. Lo demás (mirar el tablón, un encargo) funciona en solo lectura.
15
+ *
16
+ * Dos cosas que distinguen este tablón del de antes y que las herramientas
17
+ * dicen: en el escrow, el cliente de estas tareas es PanalTablon, así que
18
+ * aprobar y disputar se hacen a través de él; y los textos —anuncio, encargo,
19
+ * entrega— van por el BUZÓN DEL TABLÓN, comprobados contra los hashes que
20
+ * guarda la cadena.
21
+ */
22
+ import { type Address } from 'viem';
23
+ import { type PanalClient } from '@panal/sdk';
24
+ /** La misma forma que las herramientas de server.ts. */
25
+ export interface Herramienta {
26
+ name: string;
27
+ description: string;
28
+ inputSchema: Record<string, unknown>;
29
+ handler: (args: Record<string, unknown>) => Promise<string>;
30
+ }
31
+ export interface DepsTablon {
32
+ panal: PanalClient;
33
+ /** La wallet del servidor, si hay. */
34
+ cuenta: Address | null;
35
+ /** Motivo por el que no se puede escribir, o null. */
36
+ escrituraCerrada: () => string | null;
37
+ /** ¿Cabe este gasto en el presupuesto de su moneda? El motivo, o null. */
38
+ revisarPresupuesto: (moneda: Address, importe: bigint, simbolo: string) => string | null;
39
+ registrarGasto: (moneda: Address, importe: bigint) => void;
40
+ simbolo: (moneda: Address) => string;
41
+ usd: (importe: bigint, moneda: Address) => string;
42
+ refrescarPrecios: () => Promise<void>;
43
+ log: (m: string) => void;
44
+ explorer: string;
45
+ }
46
+ /**
47
+ * Por qué falló, en una línea. Si lo rechazó el contrato, con su error por el
48
+ * nombre y en claro: la primera línea de viem solo dice «reverted», y el
49
+ * motivo —que es lo que permite decidir qué hacer— venía debajo.
50
+ */
51
+ export declare function fallo(err: unknown): string;
52
+ /** «3 h» o «2 d». */
53
+ export declare function duracion(segundos: number): string;
54
+ /** «codigo», «código», «2» → 2. `null` si no es una categoría. */
55
+ export declare function categoriaDeTexto(entrada: string): number | null;
56
+ export declare function herramientasDelTablon(d: DepsTablon): {
57
+ lectura: Herramienta[];
58
+ escritura: Herramienta[];
59
+ };
60
+ /**
61
+ * Lo que el tablón le debe a una wallet, por moneda (MON y $PANAL). Para
62
+ * `panal_wallet` y `panal_withdraw`: es dinero suyo que no aparece en el saldo.
63
+ */
64
+ export declare function deudaDelTablon(panal: PanalClient, quien: Address): Promise<{
65
+ moneda: Address;
66
+ importe: bigint | null;
67
+ }[]>;