xonecode 0.8.25 → 0.8.27

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.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Cuándo adelgazar el `checkpoint.sqlite` de un proyecto, y qué se queda dentro.
3
+ *
4
+ * **El problema, medido**: un fichero de un proyecto real llegó a 918 MB con UNA sesión
5
+ * dentro. No es basura acumulada — es que un checkpoint guarda la lista de mensajes ENTERA y
6
+ * se escribe uno por paso del grafo, así que el coste crece con el CUADRADO de la
7
+ * conversación: el primer checkpoint de esa sesión ocupaba 3 KB y el mayor 3.885 KB, con
8
+ * 2.794 pasos. Y el 70 % no era del hilo: eran los 21 espacios `tools:*` de los subagentes,
9
+ * cada delegación con su propia cadena.
10
+ *
11
+ * **Y no hay nada oficial que adoptar.** Comprobado contra el paquete instalado, no contra un
12
+ * blog: `BaseCheckpointSaver` declara cinco métodos —`getTuple`, `list`, `put`, `putWrites`,
13
+ * `deleteThread`— y ninguno poda; no existe `prune()` en JavaScript, y el único `ttl` del
14
+ * paquete es el de `InMemoryCache`, que es la caché de un NODO y otra cosa. La doc de
15
+ * persistencia reconoce el problema («prune old checkpoints periodically») sin dar una sola
16
+ * API ni decir qué filas hacen falta para reanudar, y el issue de langgraph.js que pregunta
17
+ * exactamente esto sigue abierto y sin contestar. Como el `SqliteSaver` es NUESTRO
18
+ * (`vendor/sqliteSaver.ts`), el SQL lo escribimos aquí y no se espera a nadie.
19
+ *
20
+ * Este módulo es la REGLA y no toca disco: decide SI se poda. El SQL vive en
21
+ * `agent/sesiones/checkpointer.ts`, que es quien ya tiene la conexión.
22
+ */
23
+ /**
24
+ * A partir de cuánto estorba, en bytes.
25
+ *
26
+ * **Dimensionada con lo medido, no elegida**: una sesión de 2.794 pasos YA PODADA ocupa
27
+ * ~21 MB, así que 256 MB deja un orden de magnitud de margen y esto no puede cortar por lo
28
+ * sano en un proyecto que se porta bien. Es un TECHO, no un objetivo: un proyecto que nunca
29
+ * lo alcance no ejecuta la poda jamás.
30
+ *
31
+ * **Y la histéresis sale gratis de aquí**: podar deja el fichero en decenas de MB, o sea muy
32
+ * por debajo de la cota, así que no puede reengancharse turno tras turno. El propio tamaño es
33
+ * el estado, y por eso esto no necesita un fichero de marca ni recordar cuándo se podó — que
34
+ * es justo el trasto que se acumula en este repo.
35
+ */
36
+ export const COTA_DE_PODA_BYTES = 256 * 1024 * 1024;
37
+ /**
38
+ * ¿Toca podar?
39
+ *
40
+ * Dos condiciones, y la segunda es la que hace esto seguro:
41
+ *
42
+ * - **Pasa de la cota.** Si no, no hay nada que arreglar y mirar el tamaño cuesta un
43
+ * `statSync`, o sea nada.
44
+ * - **No hay NADA pendiente.** Un subagente parado en una aprobación vive en su espacio
45
+ * `tools:*`, y podarlo a media aprobación se lleva la reanudación por delante. Es un
46
+ * predicado sobre datos que la base ya tiene, no una heurística.
47
+ *
48
+ * **Fail-closed: lo que no se sabe NO se poda** (`hayPendientes` ausente). La dirección
49
+ * segura aquí es la que no borra: un fichero grande es un incordio, y una sesión que no
50
+ * reanuda es trabajo perdido. La misma regla que «un verificador que no corrió no es verde».
51
+ */
52
+ export function debePodar(situacion) {
53
+ if (situacion.hayPendientes !== false)
54
+ return false;
55
+ return situacion.bytes > COTA_DE_PODA_BYTES;
56
+ }
57
+ /**
58
+ * Lo que se cuenta de una poda, y **solo cuando cambió algo de verdad**.
59
+ *
60
+ * Un mantenimiento que anuncia su propia rutina es un aviso que enseña a ignorar los avisos,
61
+ * que es lo que la bitácora existe para evitar: si no se recuperó nada, esto se calla. Y no
62
+ * lleva la RUTA del fichero, porque esta línea puede viajar por el cable (`sinRutas`).
63
+ */
64
+ export function resumenDePoda(r) {
65
+ const liberado = r.antes - r.despues;
66
+ if (liberado <= 0)
67
+ return undefined;
68
+ return (`memoria del agente compactada: ${enMb(r.antes)} → ${enMb(r.despues)} ` +
69
+ `(${r.checkpointsBorrados} checkpoints y ${r.writesBorrados} escrituras que ya no hacían falta)`);
70
+ }
71
+ function enMb(bytes) {
72
+ return `${Math.round((bytes / 1024 / 1024) * 10) / 10} MB`;
73
+ }
74
+ //# sourceMappingURL=podaDeCheckpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"podaDeCheckpoint.js","sourceRoot":"","sources":["../../src/core/podaDeCheckpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAYpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,SAA4B;IACpD,IAAI,SAAS,CAAC,aAAa,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACpD,OAAO,SAAS,CAAC,KAAK,GAAG,kBAAkB,CAAC;AAC9C,CAAC;AASD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,CAAkB;IAC9C,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;IACrC,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO,CACL,kCAAkC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG;QACvE,IAAI,CAAC,CAAC,mBAAmB,kBAAkB,CAAC,CAAC,cAAc,qCAAqC,CACjG,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Qué se le deja escribir a un agente, mirando el CONTENIDO antes de escribirlo.
3
+ *
4
+ * XOne ignora en silencio lo desconocido: un atributo inventado, un `<frame>` sin cerrar o un
5
+ * `font-size` en vez de `fontsize` no dan error, dan un bug mudo. Esa es la regla que gobierna
6
+ * este harness entero, y hasta ahora solo la sostenían el prompt y el verificador del final
7
+ * del turno. Esto la mueve al momento de escribir.
8
+ *
9
+ * **LA DECISIÓN QUE HACE QUE ESTO SEA USABLE: solo se rechaza lo que la escritura INTRODUCE.**
10
+ *
11
+ * Medido sobre siete proyectos reales antes de escribir una línea: con las reglas del linter
12
+ * ya corregidas siguen quedando hallazgos en ficheros que están en producción y funcionan. Si
13
+ * la guarda mirase solo el contenido nuevo, el día que entrara no se podría editar un fichero
14
+ * que ya tuviera un defecto —el agente vendría a arreglar otra cosa y se le rechazaría por
15
+ * algo que no escribió—. Esa es la guarda que se desactiva al segundo día.
16
+ *
17
+ * Así que se compara la HUELLA de antes con la de después, que es exactamente lo que
18
+ * `agent/turno/turnoReal.ts#conVerificacion` ya hace con el simulador: la huella son los
19
+ * hallazgos identificados por código+línea, y lo que estaba antes se arrastra sin estorbar.
20
+ *
21
+ * **Y la dirección de los dos fallos posibles NO es la misma**, que es la otra mitad del
22
+ * diseño: si falla el CONTENIDO se rechaza (fail-closed); si falla el VALIDADOR —no está la
23
+ * librería, revienta el parser— se DEJA ESCRIBIR y se cuenta. Un fallo del entorno no puede
24
+ * convertirse en un veredicto sobre el trabajo del agente; es la misma frontera que
25
+ * `ErrorDelSimulador` frente a un informe en rojo.
26
+ */
27
+ /**
28
+ * La huella de un hallazgo: lo que lo identifica entre dos versiones del mismo fichero.
29
+ *
30
+ * **Código y línea, nunca el mensaje.** El mensaje lleva el nombre de la prop o el fragmento,
31
+ * así que cambia en cuanto se toca la línea de al lado y haría «nuevo» a un hallazgo que
32
+ * estaba. Es la misma huella que usa el verificador del turno.
33
+ */
34
+ function huella(h) {
35
+ return `${h.codigo}|${h.linea ?? ""}`;
36
+ }
37
+ /**
38
+ * Compara el fichero que hay con el que se quiere escribir.
39
+ *
40
+ * `antes` ausente es un fichero NUEVO: ahí no hay nada preexistente y todo hallazgo es de esta
41
+ * escritura. Es el caso en el que la guarda muerde más, y es el correcto — un fichero que se
42
+ * crea ya roto no tiene ninguna excusa.
43
+ *
44
+ * Un hallazgo repetido cuenta las veces que esté: si antes había un `font-size` y ahora hay
45
+ * dos, el segundo es nuevo. Por eso se descuentan por multiplicidad y no con un `Set`, que
46
+ * dejaría colar todas las repeticiones a partir de la primera.
47
+ */
48
+ export function veredictoDeEscritura(antes, despues) {
49
+ const disponibles = new Map();
50
+ for (const h of antes ?? [])
51
+ disponibles.set(huella(h), (disponibles.get(huella(h)) ?? 0) + 1);
52
+ const introducidos = [];
53
+ const preexistentes = [];
54
+ for (const h of despues) {
55
+ const clave = huella(h);
56
+ const quedan = disponibles.get(clave) ?? 0;
57
+ if (quedan > 0) {
58
+ disponibles.set(clave, quedan - 1);
59
+ preexistentes.push(h);
60
+ }
61
+ else {
62
+ introducidos.push(h);
63
+ }
64
+ }
65
+ return { introducidos, preexistentes };
66
+ }
67
+ /**
68
+ * Cuántos hallazgos se enseñan en el rechazo.
69
+ *
70
+ * Esto acaba dentro del contexto de un modelo, así que tiene tope por lo mismo que las
71
+ * observaciones del crítico visual: para que quepa. Lo que no cabe se CUENTA, que una lista
72
+ * recortada en silencio se lee como la lista entera.
73
+ */
74
+ export const TOPE_DE_HALLAZGOS = 10;
75
+ /**
76
+ * El texto del rechazo, que es lo único que el modelo va a leer.
77
+ *
78
+ * Tres cosas deliberadas:
79
+ *
80
+ * - **Dice que NO se ha escrito nada.** Sin eso, un modelo que recibe un error se queda sin
81
+ * saber si el fichero quedó a medias y lo siguiente que hace es releerlo para averiguarlo.
82
+ * - **Lleva el código, la línea y el mensaje** —que ya trae el arreglo cuando se sabe, como
83
+ * «"font-size" … el atributo que hace esto en XOne es "fontsize"»—. Es el patrón de
84
+ * `xone_navegacion`: el paso siguiente escrito, no un consejo.
85
+ * - **Y NO nombra lo preexistente.** Eso es ruido para quien viene a arreglar otra cosa, y
86
+ * peor: invita a tocar lo que nadie ha pedido, que es la deriva que este repo ya ha pagado
87
+ * tres veces.
88
+ */
89
+ export function motivoDelRechazo(ruta, introducidos) {
90
+ const visibles = introducidos.slice(0, TOPE_DE_HALLAZGOS);
91
+ const lineas = [
92
+ `No he escrito «${ruta}»: el contenido introduce ${introducidos.length === 1 ? "un problema" : `${introducidos.length} problemas`} que antes no estaba${introducidos.length === 1 ? "" : "n"}.`,
93
+ "El fichero se queda EXACTAMENTE como estaba — no hay nada a medias.",
94
+ "",
95
+ ];
96
+ for (const h of visibles) {
97
+ lineas.push(`- ${h.codigo}${h.linea === undefined ? "" : ` (línea ${h.linea})`}: ${h.mensaje}`);
98
+ }
99
+ if (introducidos.length > visibles.length) {
100
+ lineas.push(`- … y ${introducidos.length - visibles.length} más.`);
101
+ }
102
+ lineas.push("", "Corrige eso en el contenido y vuelve a escribirlo.");
103
+ return lineas.join("\n");
104
+ }
105
+ //# sourceMappingURL=validacionDeEscritura.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validacionDeEscritura.js","sourceRoot":"","sources":["../../src/core/validacionDeEscritura.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAUH;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,CAAsB;IACpC,OAAO,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;AACxC,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAwC,EACxC,OAA8B;IAE9B,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE;QAAE,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE/F,MAAM,YAAY,GAA0B,EAAE,CAAC;IAC/C,MAAM,aAAa,GAA0B,EAAE,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;YACnC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,YAAmC;IAChF,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG;QACb,kBAAkB,IAAI,6BAA6B,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,YAAY,uBAAuB,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG;QAC/L,qEAAqE;QACrE,EAAE;KACH,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,SAAS,YAAY,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,oDAAoD,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC"}
@@ -61,7 +61,8 @@ import { motivoDeClaveInaceptable } from "../../core/config.js";
61
61
  import { aplicarAuth, aplicarCredencialAlProceso, cargar, guardarModeloGlobal, } from "../../agent/config/configEnDisco.js";
62
62
  import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDisco.js";
63
63
  import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
64
- import { crearCheckpointerDeProyecto, hayCheckpoint, olvidarHilo, } from "../../agent/sesiones/checkpointer.js";
64
+ import { crearCheckpointerDeProyecto, hayCheckpoint, mantenimientoDelCheckpointer, olvidarHilo, } from "../../agent/sesiones/checkpointer.js";
65
+ import { resumenDePoda } from "../../core/podaDeCheckpoint.js";
65
66
  import { cargarSettings, guardarConcurrenciaDeTareas, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/config/settingsEnDisco.js";
66
67
  import { dentroDelWorkspace, expandirConCasa, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
67
68
  import { esModoDeEscritura, MODO_POR_OMISION, } from "../../core/modoDeEscritura.js";
@@ -4156,6 +4157,27 @@ export function commitDeTurnoCableado(opciones) {
4156
4157
  return hecho.via === "fallo" ? `no se pudo commitear el turno: ${hecho.motivo}` : undefined;
4157
4158
  };
4158
4159
  }
4160
+ /**
4161
+ * El mantenimiento de la memoria del agente, cableado — y EXTRAÍDO por el motivo de siempre:
4162
+ * compuesto dentro del cierre de `arrancarConsolaWeb`, que todos sus tests doblan, esta regla
4163
+ * estaría escrita y no probada. Es la novena vez que este repo tropieza con lo mismo.
4164
+ *
4165
+ * Lo que aquí se puede caer sin que TypeScript diga nada son las DOS mitades: pedir el
4166
+ * checkpointer del proyecto que TOCA —`crearCheckpointerDeProyecto` devuelve la conexión
4167
+ * compartida, y una segunda competiría con nosotros mismos por el lock exclusivo del
4168
+ * `VACUUM`— y devolver el resumen en vez de tragárselo, que es lo único que hace visible que
4169
+ * esto corrió.
4170
+ *
4171
+ * `podar` entra por parámetro para poder mirarlo desde fuera sin una base delante.
4172
+ */
4173
+ export function mantenimientoDeMemoriaCableado(opciones) {
4174
+ const podar = opciones?.podar ?? mantenimientoDelCheckpointer;
4175
+ const abrir = opciones?.checkpointer ?? crearCheckpointerDeProyecto;
4176
+ return (raiz) => {
4177
+ const hecho = podar(abrir(raiz), raiz);
4178
+ return hecho === undefined ? undefined : resumenDePoda(hecho);
4179
+ };
4180
+ }
4159
4181
  /**
4160
4182
  * La mudanza de una vez de las copias que siguen en el reparto viejo, cableada — y extraída
4161
4183
  * por el motivo de siempre: compuesta dentro del cierre de `arrancarConsolaWeb`, que todos
@@ -4795,6 +4817,9 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
4795
4817
  commitearTurno: commitDeTurnoCableado({
4796
4818
  base: () => cargarSettings().settings.workspace ?? baseDeWorkspacePorOmision(),
4797
4819
  }),
4820
+ // Y el mantenimiento de esa memoria: pasada la cota, se poda lo que ya no reanuda. La
4821
+ // composición está EXTRAÍDA y probada (`mantenimientoDeMemoriaCableado`).
4822
+ mantenerMemoria: mantenimientoDeMemoriaCableado(),
4798
4823
  olvidarMarcaDeSesion: olvidarSesion,
4799
4824
  // La memoria del agente por hilo. `historica` deja de ser «se reabrió» para ser «no hay
4800
4825
  // checkpoint que cargar», y borrar una sesión se lleva también su checkpoint.