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 +61 -20
- package/dist/limits.js +15 -6
- package/dist/server.js +58 -44
- package/dist/tablon.d.ts +67 -0
- package/dist/tablon.js +1004 -0
- package/package.json +2 -2
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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": "
|
|
127
|
-
"MCP_DAILY_BUDGET_WEI": "
|
|
128
|
-
"MCP_MAX_PER_TASK_PANAL_WEI": "
|
|
129
|
-
"MCP_DAILY_BUDGET_PANAL_WEI": "
|
|
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` | `
|
|
143
|
-
| `MCP_DAILY_BUDGET_WEI` | `
|
|
144
|
-
| `MCP_MAX_PER_TASK_PANAL_WEI` | `
|
|
145
|
-
| `MCP_DAILY_BUDGET_PANAL_WEI` | `
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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 —
|
|
1401
|
-
'
|
|
1402
|
-
'
|
|
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
|
-
|
|
1431
|
-
|
|
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 ${
|
|
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 ${
|
|
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
|
|
1457
|
-
'
|
|
1458
|
-
'and
|
|
1459
|
-
'
|
|
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
|
|
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
|
}
|
package/dist/tablon.d.ts
ADDED
|
@@ -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
|
+
}[]>;
|