xonecode 0.33.0 → 0.35.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.
Files changed (104) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-H_kGeZt2.js → EditorDeFichero-CGZ6uZPa.js} +1 -1
  2. package/apps/web/dist/assets/index-BBQH-l_3.css +1 -0
  3. package/apps/web/dist/assets/index-DqYXrrxo.js +499 -0
  4. package/apps/web/dist/assets/{php-CMFQiZgk.js → php-DTPkbJvq.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-Dj3BOBHc.js → ruby-D1wBOK7G.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/config/fuentesEnRed.js +80 -0
  8. package/dist/agent/config/fuentesEnRed.js.map +1 -0
  9. package/dist/agent/dispositivos/cajasDeMaqueta.js +90 -2
  10. package/dist/agent/dispositivos/cajasDeMaqueta.js.map +1 -1
  11. package/dist/agent/dispositivos/geometriaEnDisco.js +85 -3
  12. package/dist/agent/dispositivos/geometriaEnDisco.js.map +1 -1
  13. package/dist/agent/grafo/criticaVisual.js +43 -37
  14. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  15. package/dist/agent/grafo/fuentesDeGoogle.js +133 -0
  16. package/dist/agent/grafo/fuentesDeGoogle.js.map +1 -0
  17. package/dist/agent/grafo/incorporarAdjunto.js +52 -42
  18. package/dist/agent/grafo/incorporarAdjunto.js.map +1 -1
  19. package/dist/agent/grafo/perfiles.js +2 -0
  20. package/dist/agent/grafo/perfiles.js.map +1 -1
  21. package/dist/agent/motores/trueforge/capacidades.js +9 -6
  22. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  23. package/dist/agent/motores/trueforge/informesDeHijos.js +39 -7
  24. package/dist/agent/motores/trueforge/informesDeHijos.js.map +1 -1
  25. package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js +75 -37
  26. package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js.map +1 -1
  27. package/dist/agent/motores/trueforge/memoriaTrueforge.js +24 -4
  28. package/dist/agent/motores/trueforge/memoriaTrueforge.js.map +1 -1
  29. package/dist/agent/motores/trueforge/modeloLangchain.js +28 -4
  30. package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
  31. package/dist/agent/motores/trueforge/proponerTareas.js +67 -0
  32. package/dist/agent/motores/trueforge/proponerTareas.js.map +1 -0
  33. package/dist/agent/motores/trueforge/sesionTrueforge.js +159 -15
  34. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  35. package/dist/agent/sesiones/adjuntosDeSesion.js +68 -1
  36. package/dist/agent/sesiones/adjuntosDeSesion.js.map +1 -1
  37. package/dist/agent/subagentes/agentesEnDisco.js +12 -4
  38. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  39. package/dist/agent/tareas/aumentador.js +4 -3
  40. package/dist/agent/tareas/aumentador.js.map +1 -1
  41. package/dist/agent/tareas/juezDeTarea.js +53 -1
  42. package/dist/agent/tareas/juezDeTarea.js.map +1 -1
  43. package/dist/agent/turno/informeDeTraza.js +2 -1
  44. package/dist/agent/turno/informeDeTraza.js.map +1 -1
  45. package/dist/agent/turno/interrupts.js +12 -0
  46. package/dist/agent/turno/interrupts.js.map +1 -1
  47. package/dist/agent/turno/resumenDeTool.js +4 -0
  48. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  49. package/dist/agent/turno/turnoReal.js +3 -0
  50. package/dist/agent/turno/turnoReal.js.map +1 -1
  51. package/dist/cli/consola.js.map +1 -1
  52. package/dist/cli/main.js +4 -0
  53. package/dist/cli/main.js.map +1 -1
  54. package/dist/cli/tui/transcript.js +5 -0
  55. package/dist/cli/tui/transcript.js.map +1 -1
  56. package/dist/core/actos.js.map +1 -1
  57. package/dist/core/agentes.js +10 -0
  58. package/dist/core/agentes.js.map +1 -1
  59. package/dist/core/analisisDeSesion.js +1 -1
  60. package/dist/core/analisisDeSesion.js.map +1 -1
  61. package/dist/core/descripcionDePantalla.js +26 -13
  62. package/dist/core/descripcionDePantalla.js.map +1 -1
  63. package/dist/core/entrega.js +31 -2
  64. package/dist/core/entrega.js.map +1 -1
  65. package/dist/core/estiloMedido.js +21 -1
  66. package/dist/core/estiloMedido.js.map +1 -1
  67. package/dist/core/fuentes.js +174 -0
  68. package/dist/core/fuentes.js.map +1 -0
  69. package/dist/core/geometriaDePantalla.js +101 -1
  70. package/dist/core/geometriaDePantalla.js.map +1 -1
  71. package/dist/core/modoDeEscritura.js +4 -1
  72. package/dist/core/modoDeEscritura.js.map +1 -1
  73. package/dist/core/notify.js +4 -0
  74. package/dist/core/notify.js.map +1 -1
  75. package/dist/core/ports.js +35 -0
  76. package/dist/core/ports.js.map +1 -1
  77. package/dist/core/repartoDeEncargo.js +110 -0
  78. package/dist/core/repartoDeEncargo.js.map +1 -0
  79. package/dist/core/shellDeAgente.js +23 -0
  80. package/dist/core/shellDeAgente.js.map +1 -1
  81. package/dist/core/tareas.js +62 -1
  82. package/dist/core/tareas.js.map +1 -1
  83. package/dist/core/turno.js +15 -0
  84. package/dist/core/turno.js.map +1 -1
  85. package/dist/web/servidor/arranque.js +171 -14
  86. package/dist/web/servidor/arranque.js.map +1 -1
  87. package/dist/web/servidor/consolaWeb.js +5 -0
  88. package/dist/web/servidor/consolaWeb.js.map +1 -1
  89. package/dist/web/servidor/corredorDeTareas.js +15 -2
  90. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  91. package/dist/web/servidor/interrupcion.js +52 -0
  92. package/dist/web/servidor/interrupcion.js.map +1 -0
  93. package/dist/web/servidor/pielWeb.js +36 -0
  94. package/dist/web/servidor/pielWeb.js.map +1 -1
  95. package/dist/web/servidor/transporte.js +1 -0
  96. package/dist/web/servidor/transporte.js.map +1 -1
  97. package/dist/web/servidor/vestibulo.js +43 -2
  98. package/dist/web/servidor/vestibulo.js.map +1 -1
  99. package/package.json +1 -1
  100. package/skills/xone-development/references/css/de-una-maqueta-a-xone.md +6 -4
  101. package/skills/xone-development/references/xml-ui/atributos-coll-group-frame.md +4 -4
  102. package/skills/xone-development/references/xml-ui/nodos-group-y-frame.md +4 -4
  103. package/apps/web/dist/assets/index-CWBaotdZ.css +0 -1
  104. package/apps/web/dist/assets/index-DnGuOvbb.js +0 -499
@@ -59,16 +59,20 @@ import { anuncioDeSkills } from "./skillsTrueforge.js";
59
59
  import { capabilitiesDe, capacidadDeFecha, capacidadDeFicheros, capacidadDeAvisoDeVueltas, capacidadDeInstrucciones, capacidadDeNotas, capacidadDeConectores, capacidadDePropias, capacidadDeRecortes, capacidadesDelEspecialista, clasesDeTools, toolsDe, } from "./capacidades.js";
60
60
  import { crearConectoresDeSesion, tarjetaDeRemota } from "./toolsDeConectores.js";
61
61
  import { CONECTOR_STITCH, crearTraerDeStitch } from "./traerDeStitch.js";
62
+ import { crearProponerTareas, REPARTIR_EN_TAREAS } from "./proponerTareas.js";
63
+ import { avisoDeReparto } from "../../../core/repartoDeEncargo.js";
64
+ import { leerTareasDelPlan, RUTA_DE_TASKS } from "../../../core/tareasDelPlan.js";
62
65
  import { esDeLaMaqueta } from "../../../core/stitch.js";
63
66
  import { recibeConectores } from "../../../core/conectores.js";
64
67
  import { buscarMaqueta, medirContraMaqueta, resumenDeMedida, textoDeMedidaAutomatica, ultimaCaptura } from "./medidaAutomatica.js";
65
- import { anotarEscritura, capacidadDeInformesDeHijos, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
68
+ import { anotarEscritura, capacidadDeInformesDeHijos, esEscrituraBinaria, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
66
69
  import { crearDiagnosticoDeTools } from "../../turno/diagnosticoDeTools.js";
67
70
  import { encenderTrazaDeErrores } from "../../trazaDeErroresEnDisco.js";
68
71
  import { anotarPaso } from "../../../core/trazaDeErrores.js";
69
72
  import { entornoConDepuracion } from "../../turno/depuracion.js";
70
73
  import { detalleDe, parametrosDe } from "../../turno/resumenDeTool.js";
71
- import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
74
+ import { lineaDeComprobacion, TOPE_DE_COMPROBACIONES_AL_JUEZ } from "../../../core/entrega.js";
75
+ import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, saldarColgadas, textoDeInterrupcion, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
72
76
  import { crearNota, sobrantes } from "./notas.js";
73
77
  import { crearMemoriaDeEspecialistas } from "./memoriaDeEspecialistas.js";
74
78
  import { crearEsperas } from "./esperas.js";
@@ -83,13 +87,14 @@ import { hechosDelProyectoDe } from "../../navegacion/hechosEnDisco.js";
83
87
  import { conHechosDelProyecto } from "../../../core/hechosDelProyecto.js";
84
88
  import { crearBusquedaRegex } from "../../grafo/busquedaRegex.js";
85
89
  import { crearBuscarIcono, recibeBuscarIcono } from "../../grafo/buscarIcono.js";
90
+ import { crearBuscarFuente, crearTraerFuente, recibeFuentes } from "../../grafo/fuentesDeGoogle.js";
86
91
  import { crearGenerarFondoSvg, recibeGenerarFondo } from "../../grafo/generarFondoSvg.js";
87
92
  import { crearCopiarArtefacto } from "../../grafo/copiarArtefacto.js";
88
93
  import { crearMarcarCriteriosDelPlan } from "../../grafo/marcarCriteriosDelPlan.js";
89
94
  import { crearUnirSecciones } from "../../grafo/unirSecciones.js";
90
95
  import { crearIncorporarAdjunto, recibeIncorporarAdjunto } from "../../grafo/incorporarAdjunto.js";
91
96
  import { crearCriticaVisual } from "../../grafo/criticaVisual.js";
92
- import { crearCompararCapturas } from "../../grafo/compararCapturas.js";
97
+ import { crearCompararCapturas, NOMBRE_COMPARAR_CAPTURAS } from "../../grafo/compararCapturas.js";
93
98
  import { crearAtributosXone, NOMBRE_ATRIBUTOS_XONE } from "../../grafo/atributosXone.js";
94
99
  import { crearDiferenciaDeCapturas } from "../../grafo/diferenciaDeCapturas.js";
95
100
  import { crearLectorDeReferencias, crearLocalizadorDeHtmlDeMaqueta } from "../../grafo/lectorDeReferencias.js";
@@ -272,7 +277,7 @@ export function textoDelBucle(llama, opciones = {}) {
272
277
  ...(puedeDiseñar
273
278
  ? [
274
279
  "El LAYOUT y el CSS son TUYOS: si la pantalla se ve mal, lo arreglas tú, no se lo pasas a nadie ni lo devuelves al que te",
275
- "encargó. `designer-xone` solo hace RECURSOS —iconos de una biblioteca y fondos SVG en `icons/`—: pídeselos juntos, con para",
280
+ "encargó. `designer-xone` solo hace RECURSOS —iconos de una biblioteca y fondos SVG en `icons/`, y las fuentes en `fonts/`—: pídeselos juntos, con para",
276
281
  "qué control, tamaño en píxeles y colores, y usa los nombres que te devuelva. No le pidas que toque el `.xne` ni el CSS: no puede.",
277
282
  "Los recursos te llegan con una TABLA (fichero → control → atributo → tamaño → si lleva el símbolo dentro): úsala y no abras",
278
283
  "los SVG para enterarte de lo que ya dice.",
@@ -294,8 +299,18 @@ export function textoDelBucle(llama, opciones = {}) {
294
299
  "de la tarea en el `TASKS.md` comprobadas, (2) la app arranca y hace lo que pide, y (3) con diseño, la distancia que mide",
295
300
  "`comparar_capturas` por debajo del 10 % en vertical y en horizontal, que ENCAJE en la pantalla (cada borde del contenido a",
296
301
  "un 3 % o menos del de la maqueta: que llegue a los lados y abajo como ella), y el crítico sin diferencias de forma ni de estructura.",
297
- "Lo que XOne NO puede reproducir no cuenta y no lo persigas: las tipografías del diseño (no hay .ttf), el desenfoque de fondo y",
298
- "las animaciones; una sombra o un degradado se aproximan con un SVG. Tienes TRES vueltas de corregir y volver a comprobar para",
302
+ // Con el puerto de fuentes la tipografía SÍ se reproduce: se trae a `fonts/` (`fuentesDeGoogle.ts`). Sin él, sigue fuera.
303
+ ...(opciones.conFuentes === true
304
+ ? [
305
+ "Lo que XOne NO puede reproducir no cuenta y no lo persigas: el desenfoque de fondo y las animaciones; una sombra o un",
306
+ "degradado se aproximan con un SVG. La TIPOGRAFÍA del diseño sí: si la maqueta declara una fuente, sus .ttf van a `fonts/`",
307
+ "(pídeselos a designer-xone con los recursos, o tráelos tú con `buscar_fuente`/`traer_fuente`) y se usa con `fontname`;",
308
+ "para verla en el aparato hay que DESPLEGAR entero. Tienes TRES vueltas de corregir y volver a comprobar para",
309
+ ]
310
+ : [
311
+ "Lo que XOne NO puede reproducir no cuenta y no lo persigas: las tipografías del diseño (no hay .ttf), el desenfoque de fondo y",
312
+ "las animaciones; una sombra o un degradado se aproximan con un SVG. Tienes TRES vueltas de corregir y volver a comprobar para",
313
+ ]),
299
314
  "llegar; si al agotarlas algo",
300
315
  "sigue sin cumplirse, devuelve el trabajo diciendo QUÉ falta, con lo que midieron el crítico y la medida tal cual. Devolver antes",
301
316
  "porque «ya funciona» es dejar el trabajo a medias. Al devolver cuenta lo que se midió: el juicio final no es tuyo, y el arnés",
@@ -329,7 +344,9 @@ export function notaDeDelegacion(agentes, opciones = {}) {
329
344
  "cada encargo a developer-xone lleva el CRITERIO ESCRITO y MEDIBLE: qué casillas del plan tiene que dejar comprobadas, qué",
330
345
  "tiene que hacer la app, y, si hay diseño, que la distancia de `comparar_capturas` quede por debajo del 10 % en vertical y en",
331
346
  "horizontal, que ENCAJE en la pantalla (cada borde a un 3 % o menos del de la maqueta) y que el crítico no señale diferencias de forma o de estructura (di dónde está la maqueta). No se puede pedir",
332
- "lo que XOne no reproduce —las tipografías del diseño, el desenfoque de fondo, las animaciones—. Sin criterio, devolverá el",
347
+ opciones.conFuentes === true
348
+ ? "lo que XOne no reproduce —el desenfoque de fondo, las animaciones—; la tipografía del diseño sí (sus .ttf a `fonts/`). Sin criterio, devolverá el"
349
+ : "lo que XOne no reproduce —las tipografías del diseño, el desenfoque de fondo, las animaciones—. Sin criterio, devolverá el",
333
350
  "trabajo en cuanto funcione. No le encargues a device-controller comprobar SU",
334
351
  "trabajo, ni a designer-xone lo visual de su pantalla (designer-xone no puede tocar un `.xne` ni el CSS): llama tú al diseñador",
335
352
  "para los RECURSOS que la pantalla necesite, antes del desarrollador. Lo que developer-xone te devuelva es lo que él vio y midió, no un veredicto: el juicio final es tuyo y del arnés.",
@@ -475,6 +492,8 @@ export async function abrirSesionTrueforge(opciones) {
475
492
  /** Lo que escribió cada hijo en su encargo, quién lo llamó, y los informes que esperan a ese padre (`informesDeHijos.ts`). */
476
493
  const escritosDeCadaHilo = new Map();
477
494
  const padreDeHilo = new Map();
495
+ /** Qué hijo nació de cada `create_sub_agent` del raíz (id de la llamada → hilo): la foto a mitad de turno lo nombra. */
496
+ const hiloDeLlamadaDelRaiz = new Map();
478
497
  const informesPendientes = new Map();
479
498
  /** Las escrituras pedidas y aún sin respuesta, por hilo e id: la respuesta de una aprobada llega en OTRO `paso`. */
480
499
  const escriturasEnVuelo = new Map();
@@ -497,9 +516,52 @@ export async function abrirSesionTrueforge(opciones) {
497
516
  /** Lo que el agente dejó en `/artefactos/` y aún no se ha anunciado: un artefacto se escribe
498
517
  * SIN aprobación, así que tiene que ANUNCIARSE, como en deepagents. */
499
518
  const artefactosPorAnunciar = [];
519
+ /** Las propuestas de reparto (`proponer_tareas`) que aún no se han anunciado: van a la persona como evento. */
520
+ const propuestasPorAnunciar = [];
521
+ /** Los planes por los que ya se recordó el reparto: una vez por plan, no en cada vuelta del analista. */
522
+ const planesRecordados = new Set();
523
+ /**
524
+ * El recordatorio de repartir (`core/repartoDeEncargo.ts#avisoDeReparto`) cuando un hijo del raíz deja escrito un
525
+ * `TASKS.md` grande, y solo si esta sesión puede proponer. Se lee el plan por el MISMO backend del agente; si no se
526
+ * puede leer, no se recuerda nada (es un recordatorio, no una regla).
527
+ */
528
+ const avisoDeRepartoTras = async (escritos) => {
529
+ if (opciones.puedeProponerTareas !== true)
530
+ return undefined;
531
+ const planes = [];
532
+ for (const ruta of escritos.keys()) {
533
+ if (!RUTA_DE_TASKS.test(ruta) || planesRecordados.has(ruta))
534
+ continue;
535
+ try {
536
+ const r = (await backend.read(ruta, 0, 100_000));
537
+ if (typeof r.content !== "string")
538
+ continue;
539
+ planes.push({ ruta, tareas: leerTareasDelPlan(r.content).tareas.length });
540
+ }
541
+ catch {
542
+ // Sin plan legible no hay recordatorio.
543
+ }
544
+ }
545
+ const aviso = avisoDeReparto(planes);
546
+ if (aviso !== undefined)
547
+ for (const p of planes)
548
+ planesRecordados.add(p.ruta);
549
+ return aviso;
550
+ };
500
551
  /** Las IMÁGENES que dejó el turno en curso, para el crítico de pantalla. Se vacía al empezar
501
552
  * cada turno: una captura de antes enseña la pantalla de antes. */
502
553
  let capturasDelTurno = [];
554
+ /** Lo que dieron las comprobaciones visuales del turno, una línea cada una, para el juez de QA (`ResultadoDeTurno`). */
555
+ let comprobacionesDelTurno = [];
556
+ const anotarComprobacion = (linea) => {
557
+ comprobacionesDelTurno = [...comprobacionesDelTurno, linea].slice(-TOPE_DE_COMPROBACIONES_AL_JUEZ);
558
+ };
559
+ function* propuestasPendientes() {
560
+ while (propuestasPorAnunciar.length > 0) {
561
+ const p = propuestasPorAnunciar.shift();
562
+ yield { tipo: "propuesta-de-tareas", motivo: p.motivo, tareas: p.tareas };
563
+ }
564
+ }
503
565
  const anotarArtefacto = (a) => {
504
566
  artefactosPorAnunciar.push(a);
505
567
  // La MAQUETA traída (`/artefactos/diseno/`) no es una captura del aparato: el crítico de pantalla
@@ -663,7 +725,8 @@ export async function abrirSesionTrueforge(opciones) {
663
725
  // La maqueta casi nunca está en /artefactos/: la trae la persona (/adjuntos/) o vive en /diseno/.
664
726
  leerReferencia: lectorDeReferencias,
665
727
  // La GEOMETRÍA: el árbol guardado con la captura, y las cajas de la maqueta si trae `code.html`.
666
- leerGeometria: async (nombre) => geometriaDeCaptura(carpetaDeHotswap(carpeta), nombre),
728
+ // Por nombre y, si se renombró o convirtió (`sips … --out menu.png`), por los PÍXELES de la original (`geometriaEnDisco.ts`).
729
+ leerGeometria: async (nombre) => geometriaDeCaptura(carpetaDeHotswap(carpeta), nombre, { carpetaDeArtefactos: carpeta, decodificar: decodificarImagen }),
667
730
  cajasDeMaqueta: async (imagen, bytes) => {
668
731
  const html = localizadorDeHtml(imagen);
669
732
  const medidas = medidasDeImagen(bytes);
@@ -719,6 +782,9 @@ export async function abrirSesionTrueforge(opciones) {
719
782
  ...herramientasDeJuicio(),
720
783
  ...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
721
784
  ...traerDeStitch(),
785
+ ...(opciones.puedeProponerTareas === true
786
+ ? [crearProponerTareas((p) => void propuestasPorAnunciar.push(p))]
787
+ : []),
722
788
  ];
723
789
  const propiasDe = (agente) => [
724
790
  crearBusquedaRegex(backend),
@@ -743,6 +809,14 @@ export async function abrirSesionTrueforge(opciones) {
743
809
  ...(opciones.iconos !== undefined && recibeBuscarIcono(agente)
744
810
  ? [crearBuscarIcono(opciones.iconos)]
745
811
  : []),
812
+ // Las fuentes de la maqueta (Google Fonts) a `fonts/`: al desarrollador y al diseñador (`recibeFuentes`), con el
813
+ // puerto. `traer_fuente` ESCRIBE un binario: `capacidadesDelEspecialista` la pone en `requireApprovalForTools`.
814
+ ...(opciones.fuentes !== undefined && recibeFuentes(agente)
815
+ ? [
816
+ crearBuscarFuente(opciones.fuentes),
817
+ crearTraerFuente(opciones.fuentes, { raiz, perfil: agente }),
818
+ ]
819
+ : []),
746
820
  // Fondos SVG: pura y sin red, sin puerto; a quien escribe el proyecto, como deepagents.
747
821
  ...(recibeGenerarFondo(agente) ? [crearGenerarFondoSvg()] : []),
748
822
  // ¿Cambió la zona tras el toque? Al conductor, que es quien saca las capturas: comprueba una
@@ -913,6 +987,9 @@ export async function abrirSesionTrueforge(opciones) {
913
987
  const hiloDelPadre = params.parent?.thread_id;
914
988
  if (hiloDelPadre !== undefined && hiloDelPadre !== "")
915
989
  padreDeHilo.set(params.threadId, hiloDelPadre);
990
+ const llamadaDelPadre = params.parent?.tool_call_id;
991
+ if (hiloDelPadre === HILO_RAIZ && llamadaDelPadre !== undefined)
992
+ hiloDeLlamadaDelRaiz.set(llamadaDelPadre, params.threadId);
916
993
  nacimientoDeHilo.set(params.threadId, Date.now());
917
994
  quienEs.set(params.threadId, agente?.nombre ?? params.request.name);
918
995
  if (agente !== undefined)
@@ -957,7 +1034,7 @@ export async function abrirSesionTrueforge(opciones) {
957
1034
  "",
958
1035
  nota,
959
1036
  anuncioDeSkills(agente, catalogo),
960
- ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined })] : []),
1037
+ ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined, conFuentes: opciones.fuentes !== undefined })] : []),
961
1038
  ]
962
1039
  .filter((l) => l !== undefined)
963
1040
  .join("\n")
@@ -1052,7 +1129,7 @@ export async function abrirSesionTrueforge(opciones) {
1052
1129
  hijosConMemoria.clear();
1053
1130
  const definicion = {
1054
1131
  modelClient: llm,
1055
- instruction: [IDIOMA_DE_LA_RESPUESTA, SIN_HABLAR_DEL_HARNESS, promptOrquestador(especialistas()), notaDeDelegacion(especialistas(), { conIconos: opciones.iconos !== undefined, conComparacion: carpeta !== undefined, conMemoria: conMemoriaDeEspecialistas, conBucle: conBucleDelDesarrollador, conEsperas })].filter((l) => l !== "").join("\n\n"),
1132
+ instruction: [IDIOMA_DE_LA_RESPUESTA, SIN_HABLAR_DEL_HARNESS, promptOrquestador(especialistas()), notaDeDelegacion(especialistas(), { conIconos: opciones.iconos !== undefined, conFuentes: opciones.fuentes !== undefined, conComparacion: carpeta !== undefined, conMemoria: conMemoriaDeEspecialistas, conBucle: conBucleDelDesarrollador, conEsperas }), opciones.puedeProponerTareas === true ? REPARTIR_EN_TAREAS : ""].filter((l) => l !== "").join("\n\n"),
1056
1133
  // Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
1057
1134
  iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
1058
1135
  };
@@ -1113,6 +1190,51 @@ export async function abrirSesionTrueforge(opciones) {
1113
1190
  * de terminal no lo pasa, la misma regla que el checkpoint de deepagents.
1114
1191
  */
1115
1192
  const persistir = opciones.hilo !== undefined;
1193
+ /**
1194
+ * **La foto A MITAD de turno.** La del `finally` no basta: un corte DURO —el proceso muere, sin
1195
+ * cierre ordenado— no pasa por él, y medido en el incidente que lo motiva, un turno de 45 minutos
1196
+ * se perdió entero, mensaje de la persona incluido, aunque los ficheros sí habían cambiado.
1197
+ * deepagents no lo sufre: su checkpointer guarda cada paso.
1198
+ *
1199
+ * Se guarda en cada añadido del RAÍZ —el primero es el mensaje de la persona— y cada vez que un
1200
+ * hijo completa una escritura, porque lo escrito no toca el contexto del raíz y la foto lo nombra.
1201
+ * `toSnapshot()` es seguro aquí: la librería SUSTITUYE el contexto (`concat`), nunca lo muta, y
1202
+ * guardar es atómico. Las tool calls abiertas se saldan con el texto de una INTERRUPCIÓN
1203
+ * (`textoDeInterrupcion`), no con el de un turno cortado: tras un corte, «vuelve a pedirlo»
1204
+ * duplicaría lo que el especialista ya dejó en el disco. El turno que acaba bien la PISA con la
1205
+ * del `finally`, así que esta foto solo se lee si el proceso murió. Nunca tumba un turno.
1206
+ */
1207
+ let ultimaAMitad;
1208
+ const escritosDeLaRama = (hijo) => {
1209
+ const deLaRama = (h) => {
1210
+ for (let actual = h; actual !== undefined; actual = padreDeHilo.get(actual))
1211
+ if (actual === hijo)
1212
+ return true;
1213
+ return false;
1214
+ };
1215
+ return [...new Set([...escritosDeCadaHilo].filter(([h]) => deLaRama(h)).flatMap(([, cambios]) => [...cambios.keys()]))];
1216
+ };
1217
+ const guardarAMitad = () => {
1218
+ if (!persistir || raizActual === undefined)
1219
+ return;
1220
+ const foto = raizActual.toSnapshot();
1221
+ const escritos = [...escritosDeCadaHilo.values()].reduce((n, cambios) => n + cambios.size, 0);
1222
+ if (ultimaAMitad?.context === foto.context && ultimaAMitad.escritos === escritos)
1223
+ return;
1224
+ ultimaAMitad = { context: foto.context, escritos };
1225
+ const textoDe = (id) => {
1226
+ const hijo = hiloDeLlamadaDelRaiz.get(id);
1227
+ if (hijo === undefined)
1228
+ return textoDeInterrupcion({ escritos: [] });
1229
+ return textoDeInterrupcion({ especialista: especialistaDeHilo.get(hijo) ?? quienEs.get(hijo) ?? hijo, escritos: escritosDeLaRama(hijo) });
1230
+ };
1231
+ try {
1232
+ guardarMemoria(raiz, hilo, { ...foto, context: saldarColgadas(foto.context, undefined, textoDe) });
1233
+ }
1234
+ catch {
1235
+ // Sin la foto de a mitad el turno sigue: lo peor es lo de antes, perderlo si el proceso muere.
1236
+ }
1237
+ };
1116
1238
  /** El aviso de una memoria que existía y no se entendió: sale en el PRIMER turno y se gasta. */
1117
1239
  let avisoDeMemoria;
1118
1240
  /**
@@ -1180,6 +1302,9 @@ export async function abrirSesionTrueforge(opciones) {
1180
1302
  async function* paso(lote, senal, pendientes, preguntas = []) {
1181
1303
  for await (const _ of orquestador.send(lote))
1182
1304
  void _;
1305
+ // Lo que acaba de entrar en el raíz —el mensaje de la persona, una aprobación, una reparación—
1306
+ // ya está a salvo antes de la primera llamada al modelo.
1307
+ guardarAMitad();
1183
1308
  const llamadas = new Map();
1184
1309
  // El razonamiento de los especialistas, juntado por hilo hasta que su mensaje se completa.
1185
1310
  const pensamientos = new Map();
@@ -1188,6 +1313,8 @@ export async function abrirSesionTrueforge(opciones) {
1188
1313
  while (!r.done) {
1189
1314
  const evento = r.value;
1190
1315
  const deHilo = evento.thread_id ?? HILO_RAIZ;
1316
+ if (deHilo === HILO_RAIZ && evento.type === "internal.agent.context.append")
1317
+ guardarAMitad();
1191
1318
  if (evento.type === "internal.agent.context.append" && Array.isArray(evento.output)) {
1192
1319
  for (const m of evento.output) {
1193
1320
  for (const t of m.tool_calls ?? []) {
@@ -1199,8 +1326,9 @@ export async function abrirSesionTrueforge(opciones) {
1199
1326
  args = {};
1200
1327
  }
1201
1328
  llamadas.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
1202
- if (t.function.name === "write_file" || t.function.name === "edit_file")
1329
+ if (t.function.name === "write_file" || t.function.name === "edit_file" || esEscrituraBinaria(t.function.name)) {
1203
1330
  escriturasEnVuelo.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
1331
+ }
1204
1332
  if (t.function.name === "xone_critica_visual")
1205
1333
  ultimaCriticaDeHilo.set(deHilo, Date.now());
1206
1334
  // La traza: la MISMA lista blanca que el evento (`detalleDe`, `parametrosDe`), nunca
@@ -1222,17 +1350,22 @@ export async function abrirSesionTrueforge(opciones) {
1222
1350
  if (evento.type === "tool.response" && evento.tool_call_id !== undefined) {
1223
1351
  // Cuánto METIÓ en el contexto lo que devolvió: los caracteres, nunca el contenido.
1224
1352
  const llamada = llamadas.get(claveDe(deHilo, evento.tool_call_id));
1353
+ // Las comprobaciones VISUALES, para el juez: lo que dijeron, medido aquí y no contado por el agente.
1354
+ if (llamada !== undefined && (llamada.nombre === NOMBRE_COMPARAR_CAPTURAS || llamada.nombre === "xone_critica_visual") && typeof evento.content === "string") {
1355
+ anotarComprobacion(lineaDeComprobacion(llamada.nombre, detalleDe(llamada.nombre, llamada.args), evento.content));
1356
+ }
1225
1357
  const chars = typeof evento.content === "string" ? evento.content.length : 0;
1226
1358
  diagnostico?.resultado?.(llamada?.nombre, llamada === undefined ? undefined : detalleDe(llamada.nombre, llamada.args), chars);
1227
1359
  // Lo que ESCRIBIÓ este hilo, para decírselo a quien lo llamó al terminar (`informesDeHijos.ts`).
1228
1360
  const escritura = escriturasEnVuelo.get(claveDe(deHilo, evento.tool_call_id));
1229
1361
  if (escritura !== undefined) {
1230
1362
  escriturasEnVuelo.delete(claveDe(deHilo, evento.tool_call_id));
1231
- if (deHilo !== HILO_RAIZ && escrituraConExito(evento.content)) {
1363
+ if (deHilo !== HILO_RAIZ && escrituraConExito(evento.content, escritura.nombre)) {
1232
1364
  const cambios = escritosDeCadaHilo.get(deHilo) ?? new Map();
1233
1365
  anotarEscritura(cambios, escritura.nombre, escritura.args);
1234
1366
  escritosDeCadaHilo.set(deHilo, cambios);
1235
1367
  ultimaEscrituraDeHilo.set(deHilo, Date.now());
1368
+ guardarAMitad();
1236
1369
  }
1237
1370
  }
1238
1371
  }
@@ -1251,8 +1384,11 @@ export async function abrirSesionTrueforge(opciones) {
1251
1384
  const nombre = especialistaDeHilo.get(deHilo);
1252
1385
  const quien = especialistas().find((a) => a.nombre === nombre);
1253
1386
  const medida = medidaTrasProbar(deHilo, hiloPadre, quien);
1254
- const texto = medida ??
1255
- textoDelInforme(quienEs.get(deHilo) ?? deHilo, escritosDeCadaHilo.get(deHilo) ?? new Map(), quien !== undefined && quien.motor === "modelo" && clasesDeTools(quien) === "escribe", ultimaMedidaDeHilo.get(deHilo));
1387
+ const escritos = escritosDeCadaHilo.get(deHilo) ?? new Map();
1388
+ const informe = medida ??
1389
+ textoDelInforme(quienEs.get(deHilo) ?? deHilo, escritos, quien !== undefined && quien.motor === "modelo" && clasesDeTools(quien) === "escribe", ultimaMedidaDeHilo.get(deHilo));
1390
+ const reparto = hiloPadre === HILO_RAIZ ? await avisoDeRepartoTras(escritos) : undefined;
1391
+ const texto = reparto === undefined ? informe : [informe, reparto].filter((t) => t !== undefined).join("\n\n");
1256
1392
  escritosDeCadaHilo.delete(deHilo);
1257
1393
  nacimientoDeHilo.delete(deHilo);
1258
1394
  if (texto !== undefined)
@@ -1307,8 +1443,10 @@ export async function abrirSesionTrueforge(opciones) {
1307
1443
  yield* eventos;
1308
1444
  while (artefactosPorAnunciar.length > 0)
1309
1445
  yield { tipo: "artefacto", artefacto: artefactosPorAnunciar.shift() };
1446
+ yield* propuestasPendientes();
1310
1447
  r = await it.next();
1311
1448
  }
1449
+ yield* propuestasPendientes();
1312
1450
  const resultado = r.value;
1313
1451
  for (const accion of resultado.required_actions ?? []) {
1314
1452
  // Dos interrupciones distintas, y no se mezclan: una APROBACIÓN decide sobre una escritura
@@ -1346,6 +1484,7 @@ export async function abrirSesionTrueforge(opciones) {
1346
1484
  const trackerAlEmpezar = { input: tracker.input, output: tracker.output, cache: tracker.cache, calls: tracker.calls };
1347
1485
  externosDelTurno = 0;
1348
1486
  capturasDelTurno = [];
1487
+ comprobacionesDelTurno = [];
1349
1488
  /** El encargo de ESTE turno tal cual se pidió, y el mismo con la última pregunta y su
1350
1489
  * respuesta al lado, que es lo que se juzga y se repara (ver `flujo`). Ausente = no consta. */
1351
1490
  let encargoDelTurno;
@@ -1575,7 +1714,7 @@ export async function abrirSesionTrueforge(opciones) {
1575
1714
  escribioProyecto = cambios.length > 0;
1576
1715
  if (!salida.limpia) {
1577
1716
  if (sinResolver > 0)
1578
- motivoSinVerificar = "quedaron escrituras sin resolver";
1717
+ motivoSinVerificar = cortadoPorTope ? `se agotó el tope de ${tope} rondas de escritura` : "quedaron escrituras sin resolver";
1579
1718
  return;
1580
1719
  }
1581
1720
  veredicto = "no-corrio";
@@ -1627,6 +1766,9 @@ export async function abrirSesionTrueforge(opciones) {
1627
1766
  try {
1628
1767
  const bytes = readFileSync(join(carpeta, captura.nombre));
1629
1768
  const visual = await opciones.criticaVisual({ base64: bytes.toString("base64"), mime: captura.mime ?? "image/png" }, captura.nombre);
1769
+ anotarComprobacion(lineaDeComprobacion("crítico de pantalla del harness, al cerrar", captura.nombre, visual.veredicto === "rojo" && visual.observaciones.length > 0
1770
+ ? `rojo, ${visual.observaciones.length} defecto(s): ${visual.observaciones.join("; ")}`
1771
+ : visual.veredicto));
1630
1772
  if (visual.veredicto === "rojo" && visual.observaciones.length > 0) {
1631
1773
  observacionesVisuales = visual.observaciones;
1632
1774
  yield {
@@ -1806,6 +1948,8 @@ export async function abrirSesionTrueforge(opciones) {
1806
1948
  cortadoPorTope,
1807
1949
  verificador: veredicto,
1808
1950
  pendientes: sinResolver,
1951
+ ...(cortadoPorTope ? { topeAgotado: tope } : {}),
1952
+ ...(comprobacionesDelTurno.length === 0 ? {} : { comprobacionesVisuales: comprobacionesDelTurno }),
1809
1953
  ...(hallazgosDelTurno.length === 0 ? {} : { hallazgos: hallazgosDelTurno }),
1810
1954
  ...(preexistentesDelTurno === undefined ? {} : { preexistentes: preexistentesDelTurno }),
1811
1955
  ...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),