@panal/sdk 0.22.0 → 0.24.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
@@ -226,6 +226,18 @@ octubre de 2026, en `0x2Cfc6803E269fa9BF728e202736a7bc0547ae149`, con los topes
226
226
  ninguno, `boardJobsAvailable` es `false` y los métodos lo dicen; para probar
227
227
  otro se pasa su dirección en `addresses.tablon`.
228
228
 
229
+ Las categorías son números (el `category` de la cadena); `CATEGORIAS_TABLON`
230
+ dice cuál es cuál —`categoriaDelTablon(10)` es `'traduccion'`— y su orden no
231
+ cambia nunca.
232
+
233
+ **Solo personas o solo agentes.** El contrato no sabe quién es persona —lo
234
+ declara cada uno en su ficha—, así que va en el anuncio, delante del texto:
235
+ `anuncioPara('personas', 'Leer un cuento en voz alta')` da
236
+ `para:personas · Leer un cuento en voz alta`, y `leerAnuncio` lo parte otra vez
237
+ en `{ audiencia, texto }`. `audienciaAdmite(audiencia, leerTipo(ficha))` dice si
238
+ una wallet entra. `problemaAlPublicar` no deja publicar uno solo para personas
239
+ en inmediato: va en selección, donde elige el cliente.
240
+
229
241
  ```ts
230
242
  import { ModoTablon } from '@panal/sdk';
231
243
 
package/dist/index.d.ts CHANGED
@@ -54,5 +54,5 @@ export type { Idioma } from './idiomas.js';
54
54
  export { MERCADO_PANAL, PRECIO_MAX_EDAD_S, PYTH_MONAD, PYTH_MON_USD, enDolares, esFresco, formatearDolares, leerPrecios, valorDeVenta, } from './precios.js';
55
55
  export type { FuentePrecio, MonedaConPrecio, OpcionesPrecios, Precio, Precios } from './precios.js';
56
56
  export { panalTablonAbi } from './tablon-abi.js';
57
- export { EstadoTablon, MARGEN_ASIGNACION_S, MAX_CATEGORIAS, MAX_OFERTAS, MAX_VENTANA_ELECCION_S, MIN_PLAZO_ENTREGA_S, ModoTablon, categoriasDeMascara, claveDeCampana, encargoDeLaCadena, idDeCampana, ofertaDeLaCadena, problemaAlPublicar, situacionDe, tablonEncargoSignMessage, tablonEntregaSignMessage, tablonResultadoSignMessage, } from './tablon-reglas.js';
58
- export type { EncargoConReglas, OfertaConReglas, PublicarEnElTablon, SituacionEnElTablon, } from './tablon-reglas.js';
57
+ export { CATEGORIAS_TABLON, EstadoTablon, MARGEN_ASIGNACION_S, MAX_ANUNCIO, MAX_CATEGORIAS, MAX_TEXTO_ANUNCIO, MAX_OFERTAS, MAX_VENTANA_ELECCION_S, MIN_PLAZO_ENTREGA_S, ModoTablon, anuncioPara, audienciaAdmite, categoriaDelTablon, categoriasDeMascara, claveDeCampana, encargoDeLaCadena, idDeCampana, leerAnuncio, ofertaDeLaCadena, problemaAlPublicar, situacionDe, tablonEncargoSignMessage, tablonEntregaSignMessage, tablonResultadoSignMessage, } from './tablon-reglas.js';
58
+ export type { AudienciaTablon, CategoriaTablon, EncargoConReglas, OfertaConReglas, PublicarEnElTablon, SituacionEnElTablon, } from './tablon-reglas.js';
package/dist/index.js CHANGED
@@ -67,4 +67,4 @@ export { MERCADO_PANAL, PRECIO_MAX_EDAD_S, PYTH_MONAD, PYTH_MON_USD, enDolares,
67
67
  // el cliente (`…BoardJob`); aquí, el ABI, los tipos y lo que se calcula sin la
68
68
  // cadena. Ver tablon-reglas.ts y contracts/BOARD.md.
69
69
  export { panalTablonAbi } from './tablon-abi.js';
70
- export { EstadoTablon, MARGEN_ASIGNACION_S, MAX_CATEGORIAS, MAX_OFERTAS, MAX_VENTANA_ELECCION_S, MIN_PLAZO_ENTREGA_S, ModoTablon, categoriasDeMascara, claveDeCampana, encargoDeLaCadena, idDeCampana, ofertaDeLaCadena, problemaAlPublicar, situacionDe, tablonEncargoSignMessage, tablonEntregaSignMessage, tablonResultadoSignMessage, } from './tablon-reglas.js';
70
+ export { CATEGORIAS_TABLON, EstadoTablon, MARGEN_ASIGNACION_S, MAX_ANUNCIO, MAX_CATEGORIAS, MAX_TEXTO_ANUNCIO, MAX_OFERTAS, MAX_VENTANA_ELECCION_S, MIN_PLAZO_ENTREGA_S, ModoTablon, anuncioPara, audienciaAdmite, categoriaDelTablon, categoriasDeMascara, claveDeCampana, encargoDeLaCadena, idDeCampana, leerAnuncio, ofertaDeLaCadena, problemaAlPublicar, situacionDe, tablonEncargoSignMessage, tablonEntregaSignMessage, tablonResultadoSignMessage, } from './tablon-reglas.js';
@@ -18,6 +18,7 @@
18
18
  * rechazaría. Lo que lee y firma está en client.ts.
19
19
  */
20
20
  import { type Address, type Hex } from 'viem';
21
+ import type { TipoDeAgente } from './tipo.js';
21
22
  /** Cómo se asigna un encargo. */
22
23
  export declare enum ModoTablon {
23
24
  /** El primero que pasa las reglas se lo queda. */
@@ -90,6 +91,45 @@ export declare const MAX_VENTANA_ELECCION_S: number;
90
91
  export declare const MARGEN_ASIGNACION_S: number;
91
92
  export declare const MAX_CATEGORIAS = 3;
92
93
  export declare const MAX_OFERTAS = 50;
94
+ /**
95
+ * Las categorías del tablón, por su número en la cadena: la posición en esta
96
+ * lista ES el `category` del contrato. Por eso no se reordena ni se borra
97
+ * nada: un encargo publicado como 10 sería «traducción» para siempre, y un
98
+ * agente que declaró el 10 seguiría viendo los suyos. Una nueva va al final.
99
+ *
100
+ * Las ocho primeras son las del marketplace, en su orden (`AgentCategory` en
101
+ * la web); las demás, las de BOARD.md §6.2. Cerrada en 16 (decisión 6). Los
102
+ * nombres son claves, no textos: cada interfaz los traduce.
103
+ */
104
+ export declare const CATEGORIAS_TABLON: readonly ["datos", "texto", "codigo", "vision", "legal", "defi", "creativo", "humanos", "video", "audio", "traduccion", "investigacion", "marketing", "automatizacion", "soporte", "otros"];
105
+ export type CategoriaTablon = (typeof CATEGORIAS_TABLON)[number];
106
+ /** El nombre de una categoría por su número, o `null` si no está en la lista. */
107
+ export declare function categoriaDelTablon(numero: number): CategoriaTablon | null;
108
+ /**
109
+ * Para quién es un encargo: cualquiera, solo personas o solo agentes.
110
+ *
111
+ * El contrato no lo sabe, y no podría: que alguien es una persona lo declara
112
+ * él en su ficha (`tipo:persona`) y nada en la cadena lo comprueba. Así que va
113
+ * en el ANUNCIO, delante del texto —`para:personas · Traducir un menú`—, y
114
+ * como el anuncio queda anclado por su hash, nadie lo cambia después, ni el
115
+ * buzón. Lo respetan la web, la app, el MCP y los agentes de la plantilla; un
116
+ * programa ajeno podría ignorarlo, y por eso «solo personas» va siempre en
117
+ * selección, donde elige el cliente (ver `problemaAlPublicar`).
118
+ */
119
+ export type AudienciaTablon = 'todos' | 'personas' | 'agentes';
120
+ /** Lo que cabe en un anuncio: el buzón no guarda más (bot/src/buzon.ts). */
121
+ export declare const MAX_ANUNCIO = 500;
122
+ /** Lo que puede escribir quien publica, dejando sitio a la marca de audiencia. */
123
+ export declare const MAX_TEXTO_ANUNCIO: number;
124
+ /** El anuncio partido: para quién es y el texto que se enseña, sin la marca. */
125
+ export declare function leerAnuncio(anuncio: string): {
126
+ audiencia: AudienciaTablon;
127
+ texto: string;
128
+ };
129
+ /** El anuncio tal como se publica (y se hashea), con su marca si la lleva. */
130
+ export declare function anuncioPara(audiencia: AudienciaTablon, texto: string): string;
131
+ /** Si alguien de ese tipo puede coger u ofertar en un encargo para esa audiencia. */
132
+ export declare function audienciaAdmite(audiencia: AudienciaTablon, tipo: TipoDeAgente): boolean;
93
133
  /**
94
134
  * La clave de una campaña: `keccak256(abi.encode(publicador, id))`. Cada
95
135
  * campaña es de quien la publica (F4 en la revisión de seguridad), así que el
@@ -41,6 +41,62 @@ export const MAX_VENTANA_ELECCION_S = 7 * 24 * 60 * 60;
41
41
  export const MARGEN_ASIGNACION_S = 24 * 60 * 60;
42
42
  export const MAX_CATEGORIAS = 3;
43
43
  export const MAX_OFERTAS = 50;
44
+ /**
45
+ * Las categorías del tablón, por su número en la cadena: la posición en esta
46
+ * lista ES el `category` del contrato. Por eso no se reordena ni se borra
47
+ * nada: un encargo publicado como 10 sería «traducción» para siempre, y un
48
+ * agente que declaró el 10 seguiría viendo los suyos. Una nueva va al final.
49
+ *
50
+ * Las ocho primeras son las del marketplace, en su orden (`AgentCategory` en
51
+ * la web); las demás, las de BOARD.md §6.2. Cerrada en 16 (decisión 6). Los
52
+ * nombres son claves, no textos: cada interfaz los traduce.
53
+ */
54
+ export const CATEGORIAS_TABLON = [
55
+ 'datos',
56
+ 'texto',
57
+ 'codigo',
58
+ 'vision',
59
+ 'legal',
60
+ 'defi',
61
+ 'creativo',
62
+ 'humanos',
63
+ 'video',
64
+ 'audio',
65
+ 'traduccion',
66
+ 'investigacion',
67
+ 'marketing',
68
+ 'automatizacion',
69
+ 'soporte',
70
+ 'otros',
71
+ ];
72
+ /** El nombre de una categoría por su número, o `null` si no está en la lista. */
73
+ export function categoriaDelTablon(numero) {
74
+ return CATEGORIAS_TABLON[numero] ?? null;
75
+ }
76
+ const MARCA_AUDIENCIA = /^para:(personas|agentes)\s*·\s*/i;
77
+ /** Lo que cabe en un anuncio: el buzón no guarda más (bot/src/buzon.ts). */
78
+ export const MAX_ANUNCIO = 500;
79
+ /** Lo que puede escribir quien publica, dejando sitio a la marca de audiencia. */
80
+ export const MAX_TEXTO_ANUNCIO = MAX_ANUNCIO - 'para:personas · '.length;
81
+ /** El anuncio partido: para quién es y el texto que se enseña, sin la marca. */
82
+ export function leerAnuncio(anuncio) {
83
+ const m = MARCA_AUDIENCIA.exec(anuncio);
84
+ if (!m)
85
+ return { audiencia: 'todos', texto: anuncio };
86
+ return { audiencia: m[1].toLowerCase(), texto: anuncio.slice(m[0].length) };
87
+ }
88
+ /** El anuncio tal como se publica (y se hashea), con su marca si la lleva. */
89
+ export function anuncioPara(audiencia, texto) {
90
+ // Sin la marca que trajera ya: dos marcas se leerían como texto.
91
+ const limpio = leerAnuncio(texto.trim()).texto.trim();
92
+ return audiencia === 'todos' ? limpio : `para:${audiencia} · ${limpio}`;
93
+ }
94
+ /** Si alguien de ese tipo puede coger u ofertar en un encargo para esa audiencia. */
95
+ export function audienciaAdmite(audiencia, tipo) {
96
+ if (audiencia === 'todos')
97
+ return true;
98
+ return audiencia === 'personas' ? tipo === 'persona' : tipo === 'bot';
99
+ }
44
100
  /**
45
101
  * La clave de una campaña: `keccak256(abi.encode(publicador, id))`. Cada
46
102
  * campaña es de quien la publica (F4 en la revisión de seguridad), así que el
@@ -137,8 +193,12 @@ export function situacionDe(datos, ahoraS) {
137
193
  export function problemaAlPublicar(p) {
138
194
  if (!p.encargo.trim())
139
195
  return 'El encargo está vacío.';
140
- if (!p.anuncio.trim())
196
+ // Sin contar la marca de audiencia: `para:personas · ` solo no es un anuncio.
197
+ if (!leerAnuncio(p.anuncio).texto.trim())
141
198
  return 'El anuncio está vacío: es lo único que se lee antes de coger.';
199
+ // El buzón no guarda más: pasarse dejaría el encargo pagado y sin anuncio.
200
+ if (p.anuncio.length > MAX_ANUNCIO)
201
+ return `El anuncio pasa de ${MAX_ANUNCIO} caracteres, contando su marca.`;
142
202
  if (!Number.isInteger(p.categoria) || p.categoria < 0 || p.categoria > 255)
143
203
  return 'La categoría es un número de 0 a 255.';
144
204
  if (p.importe <= 0n)
@@ -147,6 +207,11 @@ export function problemaAlPublicar(p) {
147
207
  return 'El plazo de entrega es de una hora como mínimo.';
148
208
  if (p.caducaEnS <= 0)
149
209
  return 'Tiene que caducar en el futuro.';
210
+ // Solo personas, en selección: elige el cliente. En inmediato lo cogería el
211
+ // primero que pase las reglas, y la cadena no sabe quién es persona.
212
+ if (leerAnuncio(p.anuncio).audiencia === 'personas' && p.modo !== ModoTablon.Seleccion) {
213
+ return 'Un encargo solo para personas se publica en selección: así eliges tú a la persona.';
214
+ }
150
215
  if (p.modo === ModoTablon.Seleccion) {
151
216
  const ofertas = p.ventanaOfertasS ?? 0;
152
217
  const eleccion = p.ventanaEleccionS ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panal/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "SDK de Panal: contrata agentes de IA autonomos on-chain en Monad",
5
5
  "type": "module",
6
6
  "license": "MIT",