@ajuarezso/lienzo-mcp 0.3.0 → 0.3.1

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/lib/esperar.mjs CHANGED
@@ -119,13 +119,18 @@ async function sondear(consultar, o, limite, consultasPrevias) {
119
119
  }
120
120
  }
121
121
 
122
- /** Respuesta de lienzo_leer / lienzo_esperar que no entregó todo (lo dice formatearLote). */
122
+ /**
123
+ * Respuesta de lienzo_leer / lienzo_esperar que no entregó todo. OJO: el texto también trae lo que escribieron otros
124
+ * (una tarjeta puede decir «Había más de 20 personas»): mejor el `recortado` de formatearLoteDetalle.
125
+ */
123
126
  export const RE_RECORTADA = /Respuesta recortada|Había más de/;
124
127
 
125
128
  /**
126
129
  * Tras formatear lo que trajo una espera con timbre: si la respuesta entró entera, lo consultado ya está
127
130
  * entregado y la próxima espera no necesita volver a mirar (ahorra una pasada por cada novedad).
131
+ * `recortado`: el booleano de formatearLoteDetalle (o, por compatibilidad, el texto, que se mira con RE_RECORTADA).
128
132
  */
129
- export function entregado(timbre, r, texto) {
130
- if (timbre && r?.modo === 'timbre' && r.version != null && !RE_RECORTADA.test(texto)) timbre.visto = r.version;
133
+ export function entregado(timbre, r, recortado) {
134
+ const corto = typeof recortado === 'boolean' ? recortado : RE_RECORTADA.test(String(recortado ?? ''));
135
+ if (timbre && r?.modo === 'timbre' && r.version != null && !corto) timbre.visto = r.version;
131
136
  }
package/lib/lienzo.mjs CHANGED
@@ -183,6 +183,7 @@ export class Lienzo {
183
183
  this.intervaloEspera = INTERVALO_MS; // lo que lleva creciendo la espera de lienzo_esperar
184
184
  this.participantes = new Map();
185
185
  this.participantesEn = 0;
186
+ this._ausentes = new Map(); // id → cuándo se confirmó que no está en la sala (no se recarga todo por él cada vez)
186
187
  this.tareasPedidas = new Map(); // id → { estado, respuesta, tomadaPor }
187
188
  this.tareasCargadas = false;
188
189
  this.listas = new Set(); // ids de las listas que pediste (sus puntos llegan agrupados)
@@ -345,8 +346,19 @@ export class Lienzo {
345
346
 
346
347
  async _participante(id) {
347
348
  if (!id) return null;
348
- if (!this.participantes.has(id)) await this._cargarParticipantes(true);
349
- return this.participantes.get(id) || null;
349
+ if (this.participantes.has(id)) return this.participantes.get(id);
350
+ // Un id que no está fuerza una recarga (quien entró recién). Pero si ya se confirmó hace poco que no está (alguien
351
+ // que salió, o un paraId inventado), no se vuelve a leer la sala entera (hasta 500) por cada tarjeta que lo nombre.
352
+ const ausente = this._ausentes.get(id);
353
+ if (ausente != null && Date.now() - ausente < RECARGA_PARTICIPANTES_MS) return null;
354
+ await this._cargarParticipantes(true);
355
+ const p = this.participantes.get(id) || null;
356
+ if (p) this._ausentes.delete(id);
357
+ else {
358
+ if (this._ausentes.size >= 500) this._ausentes.clear();
359
+ this._ausentes.set(id, Date.now());
360
+ }
361
+ return p;
350
362
  }
351
363
 
352
364
  /** Cuántos participantes de la sala se llaman igual que `id` (1 = nombre único). */
@@ -483,6 +495,7 @@ export class Lienzo {
483
495
  this.listas.clear();
484
496
  this.participantes.clear();
485
497
  this.participantesEn = 0;
498
+ this._ausentes.clear();
486
499
  }
487
500
  await this._anotarEnIndice(salaId);
488
501
  return { salaId, sala, link: this.link(salaId), yo: this.yo };
@@ -1268,9 +1281,18 @@ export class Lienzo {
1268
1281
  * (creadaEn, editadaEn y el estado visto de tus tareas) avanzan SOLO hasta lo que se entregó, y un
1269
1282
  * bloque (una lista, o lo que nació en el mismo :commit) se entrega entero o no se entrega.
1270
1283
  */
1271
- async formatearLote(lote, { codigo = nuevoCodigo(), max = MAX_RESPUESTA } = {}) {
1272
- if (lote.una) return this._formatearUna(lote.una, { codigo, max, desdeCaracter: lote.desdeCaracter });
1273
- if (lote.lista) return `${aviso(codigo)}\n\n${await this._formatearLista(lote.lista.padre, lote.lista.puntos, { codigo, max })}`;
1284
+ async formatearLote(lote, o = {}) {
1285
+ return (await this.formatearLoteDetalle(lote, o)).texto;
1286
+ }
1287
+
1288
+ /**
1289
+ * formatearLote con el dato de si quedó algo sin entregar (`recortado`: tope de caracteres o más de
1290
+ * LIMITE_CONSULTA tarjetas), sin tener que buscarlo en el texto (que también trae lo que escribieron otros).
1291
+ * sinPie: sin los párrafos finales («Respuesta recortada…», «Había más de…», «marca: …»), para el canal.
1292
+ */
1293
+ async formatearLoteDetalle(lote, { codigo = nuevoCodigo(), max = MAX_RESPUESTA, sinPie = false } = {}) {
1294
+ if (lote.una) return { texto: await this._formatearUna(lote.una, { codigo, max, desdeCaracter: lote.desdeCaracter }), recortado: false };
1295
+ if (lote.lista) return { texto: `${aviso(codigo)}\n\n${await this._formatearLista(lote.lista.padre, lote.lista.puntos, { codigo, max })}`, recortado: false };
1274
1296
  const { tarjetas = [], editadas = [], cambios = [], soloAjenas = false, lleno = false } = lote;
1275
1297
  const nuevas = new Set(tarjetas.map((t) => t.id));
1276
1298
  const items = [
@@ -1310,6 +1332,8 @@ export class Lienzo {
1310
1332
  if (bloques.editadas.length) partes.push(`Tarjetas editadas (${piezas.editadas}):`, ...bloques.editadas);
1311
1333
  if (bloques.tarjetas.length) partes.push(`Tarjetas (${piezas.tarjetas}):`, ...bloques.tarjetas);
1312
1334
  if (!entregados && !cortado) partes.push('No hay tarjetas nuevas.');
1335
+ const recortado = cortado || Boolean(lleno);
1336
+ if (sinPie) return { texto: partes.join('\n\n'), recortado };
1313
1337
  if (cortado) {
1314
1338
  const total = quedan.cambios + quedan.editadas + quedan.tarjetas;
1315
1339
  const desglose = [
@@ -1324,6 +1348,6 @@ export class Lienzo {
1324
1348
  }
1325
1349
  const marca = soloAjenas ? this.marca : (ultimaCreada ?? lote.desde ?? this.marca);
1326
1350
  partes.push(`marca: ${marca ?? '(sin marca)'} — pásala como «desde» para pedir solo lo posterior.`);
1327
- return partes.join('\n\n');
1351
+ return { texto: partes.join('\n\n'), recortado };
1328
1352
  }
1329
1353
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ajuarezso/lienzo-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Servidor MCP de Lienzo: une tu agente (Claude Code u otro cliente MCP) a una sala compartida con los agentes de otras personas. / MCP server that joins your agent to a room shared with other people's agents.",
5
5
  "keywords": [
6
6
  "mcp",