xonecode 0.31.0 → 0.34.2

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 (133) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-ChyJtiiz.js → EditorDeFichero-Cj0knNmP.js} +1 -1
  2. package/apps/web/dist/assets/index-Ga8fTlWp.js +499 -0
  3. package/apps/web/dist/assets/{index-IptObD2z.css → index-R3kXj69E.css} +1 -1
  4. package/apps/web/dist/assets/{php-B91ZcS-9.js → php-MVUx_K96.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-DrxrTSz0.js → ruby-n-r-AXdi.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/config/modelosDeMotor.js +4 -2
  10. package/dist/agent/config/modelosDeMotor.js.map +1 -1
  11. package/dist/agent/config/settingsEnDisco.js +26 -0
  12. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  13. package/dist/agent/dispositivos/cajasDeMaqueta.js +218 -0
  14. package/dist/agent/dispositivos/cajasDeMaqueta.js.map +1 -0
  15. package/dist/agent/dispositivos/geometriaEnDisco.js +107 -0
  16. package/dist/agent/dispositivos/geometriaEnDisco.js.map +1 -0
  17. package/dist/agent/dispositivos/juezVisual.js +84 -3
  18. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  19. package/dist/agent/grafo/criticaVisual.js +246 -11
  20. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  21. package/dist/agent/grafo/fuentesDeGoogle.js +133 -0
  22. package/dist/agent/grafo/fuentesDeGoogle.js.map +1 -0
  23. package/dist/agent/grafo/incorporarAdjunto.js +52 -42
  24. package/dist/agent/grafo/incorporarAdjunto.js.map +1 -1
  25. package/dist/agent/grafo/lectorDeReferencias.js +21 -0
  26. package/dist/agent/grafo/lectorDeReferencias.js.map +1 -1
  27. package/dist/agent/grafo/perfiles.js +2 -0
  28. package/dist/agent/grafo/perfiles.js.map +1 -1
  29. package/dist/agent/motores/motoresLocales.js +429 -0
  30. package/dist/agent/motores/motoresLocales.js.map +1 -0
  31. package/dist/agent/motores/trueforge/capacidades.js +9 -6
  32. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  33. package/dist/agent/motores/trueforge/informesDeHijos.js +39 -7
  34. package/dist/agent/motores/trueforge/informesDeHijos.js.map +1 -1
  35. package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js +75 -37
  36. package/dist/agent/motores/trueforge/memoriaDeEspecialistas.js.map +1 -1
  37. package/dist/agent/motores/trueforge/memoriaTrueforge.js +24 -4
  38. package/dist/agent/motores/trueforge/memoriaTrueforge.js.map +1 -1
  39. package/dist/agent/motores/trueforge/modeloLangchain.js +28 -4
  40. package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
  41. package/dist/agent/motores/trueforge/proponerTareas.js +67 -0
  42. package/dist/agent/motores/trueforge/proponerTareas.js.map +1 -0
  43. package/dist/agent/motores/trueforge/sesionTrueforge.js +133 -13
  44. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  45. package/dist/agent/motores/trueforge/toolsDeFichero.js +24 -6
  46. package/dist/agent/motores/trueforge/toolsDeFichero.js.map +1 -1
  47. package/dist/agent/sesiones/adjuntosDeSesion.js +192 -5
  48. package/dist/agent/sesiones/adjuntosDeSesion.js.map +1 -1
  49. package/dist/agent/subagentes/agentesEnDisco.js +12 -4
  50. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  51. package/dist/agent/subagentes/ejecutableEnWindows.js +139 -0
  52. package/dist/agent/subagentes/ejecutableEnWindows.js.map +1 -0
  53. package/dist/agent/subagentes/subagenteCodex.js +7 -2
  54. package/dist/agent/subagentes/subagenteCodex.js.map +1 -1
  55. package/dist/agent/subagentes/subagenteOpencode.js +12 -5
  56. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
  57. package/dist/agent/tareas/aumentador.js +4 -3
  58. package/dist/agent/tareas/aumentador.js.map +1 -1
  59. package/dist/agent/turno/informeDeTraza.js +2 -1
  60. package/dist/agent/turno/informeDeTraza.js.map +1 -1
  61. package/dist/agent/turno/interrupts.js +12 -0
  62. package/dist/agent/turno/interrupts.js.map +1 -1
  63. package/dist/agent/turno/resumenDeTool.js +4 -0
  64. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  65. package/dist/agent/turno/turnoReal.js +3 -0
  66. package/dist/agent/turno/turnoReal.js.map +1 -1
  67. package/dist/cli/consola.js.map +1 -1
  68. package/dist/cli/main.js +2 -0
  69. package/dist/cli/main.js.map +1 -1
  70. package/dist/cli/tui/transcript.js +5 -0
  71. package/dist/cli/tui/transcript.js.map +1 -1
  72. package/dist/core/actos.js.map +1 -1
  73. package/dist/core/adjuntos.js +10 -2
  74. package/dist/core/adjuntos.js.map +1 -1
  75. package/dist/core/agentes.js +10 -0
  76. package/dist/core/agentes.js.map +1 -1
  77. package/dist/core/analisisDeSesion.js +1 -1
  78. package/dist/core/analisisDeSesion.js.map +1 -1
  79. package/dist/core/descripcionDePantalla.js +367 -0
  80. package/dist/core/descripcionDePantalla.js.map +1 -0
  81. package/dist/core/estiloMedido.js +352 -0
  82. package/dist/core/estiloMedido.js.map +1 -0
  83. package/dist/core/fuentes.js +174 -0
  84. package/dist/core/fuentes.js.map +1 -0
  85. package/dist/core/geometriaDePantalla.js +537 -0
  86. package/dist/core/geometriaDePantalla.js.map +1 -0
  87. package/dist/core/motoresLocales.js +128 -0
  88. package/dist/core/motoresLocales.js.map +1 -0
  89. package/dist/core/notify.js +4 -0
  90. package/dist/core/notify.js.map +1 -1
  91. package/dist/core/nucleoXone.js +6 -6
  92. package/dist/core/nucleoXone.js.map +1 -1
  93. package/dist/core/ports.js +35 -0
  94. package/dist/core/ports.js.map +1 -1
  95. package/dist/core/repartoDeEncargo.js +87 -0
  96. package/dist/core/repartoDeEncargo.js.map +1 -0
  97. package/dist/core/settings.js +5 -0
  98. package/dist/core/settings.js.map +1 -1
  99. package/dist/core/shellDeAgente.js +36 -0
  100. package/dist/core/shellDeAgente.js.map +1 -1
  101. package/dist/core/tareas.js +7 -0
  102. package/dist/core/tareas.js.map +1 -1
  103. package/dist/core/turno.js +15 -0
  104. package/dist/core/turno.js.map +1 -1
  105. package/dist/core/zipDeAdjunto.js +79 -0
  106. package/dist/core/zipDeAdjunto.js.map +1 -0
  107. package/dist/web/servidor/arranque.js +201 -13
  108. package/dist/web/servidor/arranque.js.map +1 -1
  109. package/dist/web/servidor/consolaWeb.js +5 -0
  110. package/dist/web/servidor/consolaWeb.js.map +1 -1
  111. package/dist/web/servidor/interrupcion.js +52 -0
  112. package/dist/web/servidor/interrupcion.js.map +1 -0
  113. package/dist/web/servidor/pielWeb.js +36 -0
  114. package/dist/web/servidor/pielWeb.js.map +1 -1
  115. package/dist/web/servidor/transporte.js +1 -0
  116. package/dist/web/servidor/transporte.js.map +1 -1
  117. package/dist/web/servidor/vestibulo.js +30 -2
  118. package/dist/web/servidor/vestibulo.js.map +1 -1
  119. package/package.json +1 -1
  120. package/skills/xone-development/SKILL.md +2 -1
  121. package/skills/xone-development/references/css/buenas-practicas-y-parser.md +2 -1
  122. package/skills/xone-development/references/css/de-una-maqueta-a-xone.md +83 -0
  123. package/skills/xone-development/references/css/patrones-material-y-temas.md +15 -13
  124. package/skills/xone-development/references/css/propiedades-y-herencia.md +2 -2
  125. package/skills/xone-development/references/css/selectores-unidades-colores.md +8 -3
  126. package/skills/xone-development/references/indice-completo.md +1 -0
  127. package/skills/xone-development/references/xml-ui/atributos-coll-group-frame.md +4 -4
  128. package/skills/xone-development/references/xml-ui/nodos-group-y-frame.md +4 -4
  129. package/skills/xone-hotswap/lib/geometria.mjs +147 -0
  130. package/skills/xone-hotswap/scripts/xone-captura-android +6 -0
  131. package/skills/xone-hotswap/scripts/xone-hotswap +8 -0
  132. package/skills/xone-hotswap/scripts/xone-recargar-android +15 -0
  133. package/apps/web/dist/assets/index-CFIrUOmW.js +0 -499
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { TOPE_DE_PREGUNTAS_CONTESTADAS_SOLAS, opcionRecomendada } from "../../../core/modoDeEscritura.js";
27
27
  import { claseDeTrabajo } from "../../../core/esfuerzo.js";
28
- import { existsSync, readdirSync, readFileSync } from "node:fs";
28
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
29
29
  import { join } from "node:path";
30
30
  import winston from "winston";
31
31
  import { AgentThread, AgentThreadOrchestrator, EventType, NOOP_AGENT_TRACING, askUserQuestion, contextCompaction, dynamicSubAgents } from "./trueforge.js";
@@ -59,16 +59,17 @@ 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";
62
63
  import { esDeLaMaqueta } from "../../../core/stitch.js";
63
64
  import { recibeConectores } from "../../../core/conectores.js";
64
65
  import { buscarMaqueta, medirContraMaqueta, resumenDeMedida, textoDeMedidaAutomatica, ultimaCaptura } from "./medidaAutomatica.js";
65
- import { anotarEscritura, capacidadDeInformesDeHijos, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
66
+ import { anotarEscritura, capacidadDeInformesDeHijos, esEscrituraBinaria, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
66
67
  import { crearDiagnosticoDeTools } from "../../turno/diagnosticoDeTools.js";
67
68
  import { encenderTrazaDeErrores } from "../../trazaDeErroresEnDisco.js";
68
69
  import { anotarPaso } from "../../../core/trazaDeErrores.js";
69
70
  import { entornoConDepuracion } from "../../turno/depuracion.js";
70
71
  import { detalleDe, parametrosDe } from "../../turno/resumenDeTool.js";
71
- import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
72
+ import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, saldarColgadas, textoDeInterrupcion, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
72
73
  import { crearNota, sobrantes } from "./notas.js";
73
74
  import { crearMemoriaDeEspecialistas } from "./memoriaDeEspecialistas.js";
74
75
  import { crearEsperas } from "./esperas.js";
@@ -83,6 +84,7 @@ import { hechosDelProyectoDe } from "../../navegacion/hechosEnDisco.js";
83
84
  import { conHechosDelProyecto } from "../../../core/hechosDelProyecto.js";
84
85
  import { crearBusquedaRegex } from "../../grafo/busquedaRegex.js";
85
86
  import { crearBuscarIcono, recibeBuscarIcono } from "../../grafo/buscarIcono.js";
87
+ import { crearBuscarFuente, crearTraerFuente, recibeFuentes } from "../../grafo/fuentesDeGoogle.js";
86
88
  import { crearGenerarFondoSvg, recibeGenerarFondo } from "../../grafo/generarFondoSvg.js";
87
89
  import { crearCopiarArtefacto } from "../../grafo/copiarArtefacto.js";
88
90
  import { crearMarcarCriteriosDelPlan } from "../../grafo/marcarCriteriosDelPlan.js";
@@ -92,10 +94,13 @@ import { crearCriticaVisual } from "../../grafo/criticaVisual.js";
92
94
  import { crearCompararCapturas } from "../../grafo/compararCapturas.js";
93
95
  import { crearAtributosXone, NOMBRE_ATRIBUTOS_XONE } from "../../grafo/atributosXone.js";
94
96
  import { crearDiferenciaDeCapturas } from "../../grafo/diferenciaDeCapturas.js";
95
- import { crearLectorDeReferencias } from "../../grafo/lectorDeReferencias.js";
97
+ import { crearLectorDeReferencias, crearLocalizadorDeHtmlDeMaqueta } from "../../grafo/lectorDeReferencias.js";
98
+ import { cajasDeMaqueta, medidasDeImagen, viewportDeMaqueta } from "../../dispositivos/cajasDeMaqueta.js";
99
+ import { geometriaDeCaptura } from "../../dispositivos/geometriaEnDisco.js";
100
+ import { decodificarImagen } from "../../dispositivos/decodificarImagen.js";
96
101
  import { crearDescribirImagen } from "../../grafo/describirImagen.js";
97
102
  import { crearTraerDeLaMaquina } from "../../grafo/traerDeLaMaquina.js";
98
- import { invocarVisualConModelos } from "../../dispositivos/juezVisual.js";
103
+ import { describirPantalla, invocarVisualConModelos } from "../../dispositivos/juezVisual.js";
99
104
  import { estilosDeDisco, indiceEnDisco } from "../../navegacion/indiceEnDisco.js";
100
105
  /** El hilo raíz de TrueForge. Se llama así en la librería y no se elige. */
101
106
  const HILO_RAIZ = "main";
@@ -269,7 +274,7 @@ export function textoDelBucle(llama, opciones = {}) {
269
274
  ...(puedeDiseñar
270
275
  ? [
271
276
  "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",
272
- "encargó. `designer-xone` solo hace RECURSOS —iconos de una biblioteca y fondos SVG en `icons/`—: pídeselos juntos, con para",
277
+ "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",
273
278
  "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.",
274
279
  "Los recursos te llegan con una TABLA (fichero → control → atributo → tamaño → si lleva el símbolo dentro): úsala y no abras",
275
280
  "los SVG para enterarte de lo que ya dice.",
@@ -291,8 +296,18 @@ export function textoDelBucle(llama, opciones = {}) {
291
296
  "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",
292
297
  "`comparar_capturas` por debajo del 10 % en vertical y en horizontal, que ENCAJE en la pantalla (cada borde del contenido a",
293
298
  "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.",
294
- "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",
295
- "las animaciones; una sombra o un degradado se aproximan con un SVG. Tienes TRES vueltas de corregir y volver a comprobar para",
299
+ // Con el puerto de fuentes la tipografía SÍ se reproduce: se trae a `fonts/` (`fuentesDeGoogle.ts`). Sin él, sigue fuera.
300
+ ...(opciones.conFuentes === true
301
+ ? [
302
+ "Lo que XOne NO puede reproducir no cuenta y no lo persigas: el desenfoque de fondo y las animaciones; una sombra o un",
303
+ "degradado se aproximan con un SVG. La TIPOGRAFÍA del diseño sí: si la maqueta declara una fuente, sus .ttf van a `fonts/`",
304
+ "(pídeselos a designer-xone con los recursos, o tráelos tú con `buscar_fuente`/`traer_fuente`) y se usa con `fontname`;",
305
+ "para verla en el aparato hay que DESPLEGAR entero. Tienes TRES vueltas de corregir y volver a comprobar para",
306
+ ]
307
+ : [
308
+ "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",
309
+ "las animaciones; una sombra o un degradado se aproximan con un SVG. Tienes TRES vueltas de corregir y volver a comprobar para",
310
+ ]),
296
311
  "llegar; si al agotarlas algo",
297
312
  "sigue sin cumplirse, devuelve el trabajo diciendo QUÉ falta, con lo que midieron el crítico y la medida tal cual. Devolver antes",
298
313
  "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",
@@ -326,7 +341,9 @@ export function notaDeDelegacion(agentes, opciones = {}) {
326
341
  "cada encargo a developer-xone lleva el CRITERIO ESCRITO y MEDIBLE: qué casillas del plan tiene que dejar comprobadas, qué",
327
342
  "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",
328
343
  "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",
329
- "lo que XOne no reproduce —las tipografías del diseño, el desenfoque de fondo, las animaciones—. Sin criterio, devolverá el",
344
+ opciones.conFuentes === true
345
+ ? "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"
346
+ : "lo que XOne no reproduce —las tipografías del diseño, el desenfoque de fondo, las animaciones—. Sin criterio, devolverá el",
330
347
  "trabajo en cuanto funcione. No le encargues a device-controller comprobar SU",
331
348
  "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",
332
349
  "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.",
@@ -472,6 +489,8 @@ export async function abrirSesionTrueforge(opciones) {
472
489
  /** Lo que escribió cada hijo en su encargo, quién lo llamó, y los informes que esperan a ese padre (`informesDeHijos.ts`). */
473
490
  const escritosDeCadaHilo = new Map();
474
491
  const padreDeHilo = new Map();
492
+ /** Qué hijo nació de cada `create_sub_agent` del raíz (id de la llamada → hilo): la foto a mitad de turno lo nombra. */
493
+ const hiloDeLlamadaDelRaiz = new Map();
475
494
  const informesPendientes = new Map();
476
495
  /** Las escrituras pedidas y aún sin respuesta, por hilo e id: la respuesta de una aprobada llega en OTRO `paso`. */
477
496
  const escriturasEnVuelo = new Map();
@@ -494,9 +513,17 @@ export async function abrirSesionTrueforge(opciones) {
494
513
  /** Lo que el agente dejó en `/artefactos/` y aún no se ha anunciado: un artefacto se escribe
495
514
  * SIN aprobación, así que tiene que ANUNCIARSE, como en deepagents. */
496
515
  const artefactosPorAnunciar = [];
516
+ /** Las propuestas de reparto (`proponer_tareas`) que aún no se han anunciado: van a la persona como evento. */
517
+ const propuestasPorAnunciar = [];
497
518
  /** Las IMÁGENES que dejó el turno en curso, para el crítico de pantalla. Se vacía al empezar
498
519
  * cada turno: una captura de antes enseña la pantalla de antes. */
499
520
  let capturasDelTurno = [];
521
+ function* propuestasPendientes() {
522
+ while (propuestasPorAnunciar.length > 0) {
523
+ const p = propuestasPorAnunciar.shift();
524
+ yield { tipo: "propuesta-de-tareas", motivo: p.motivo, tareas: p.tareas };
525
+ }
526
+ }
500
527
  const anotarArtefacto = (a) => {
501
528
  artefactosPorAnunciar.push(a);
502
529
  // La MAQUETA traída (`/artefactos/diseno/`) no es una captura del aparato: el crítico de pantalla
@@ -636,6 +663,11 @@ export async function abrirSesionTrueforge(opciones) {
636
663
  ...(carpeta === undefined ? {} : { artefactos: carpeta }),
637
664
  ...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
638
665
  });
666
+ const localizadorDeHtml = crearLocalizadorDeHtmlDeMaqueta({
667
+ raiz,
668
+ ...(carpeta === undefined ? {} : { artefactos: carpeta }),
669
+ ...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
670
+ });
639
671
  /** El crítico y la medida, con las MISMAS piezas para quien las tenga: el raíz, y con el bucle encendido el desarrollador. */
640
672
  const herramientasDeJuicio = () => carpeta === undefined
641
673
  ? []
@@ -643,8 +675,28 @@ export async function abrirSesionTrueforge(opciones) {
643
675
  crearCriticaVisual({
644
676
  leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)),
645
677
  invocar: invocarVisualConModelos({ paraPapel: (p) => modelos.paraPapel(p) }),
678
+ // La maqueta y la captura, cada una descrita SOLA con el mismo esquema: geometría y luego estilo.
679
+ describirPantalla: (imagen, existen) => describirPantalla(imagen, invocarVisualConModelos({ paraPapel: (p) => modelos.paraPapel(p) }), existen),
680
+ // El estilo de cada control se MIDE en sus píxeles: hace falta sacarlos de la imagen.
681
+ decodificar: decodificarImagen,
682
+ // El informe se queda junto a la captura, para poder auditar qué se le dijo al agente.
683
+ guardarInforme: async (nombre, informe) => {
684
+ mkdirSync(carpetaDeHotswap(carpeta), { recursive: true });
685
+ writeFileSync(join(carpetaDeHotswap(carpeta), `${nombre.replace(/\.[A-Za-z0-9]{2,4}$/, "")}.critica.txt`), informe, "utf8");
686
+ },
646
687
  // La maqueta casi nunca está en /artefactos/: la trae la persona (/adjuntos/) o vive en /diseno/.
647
688
  leerReferencia: lectorDeReferencias,
689
+ // La GEOMETRÍA: el árbol guardado con la captura, y las cajas de la maqueta si trae `code.html`.
690
+ // Por nombre y, si se renombró o convirtió (`sips … --out menu.png`), por los PÍXELES de la original (`geometriaEnDisco.ts`).
691
+ leerGeometria: async (nombre) => geometriaDeCaptura(carpetaDeHotswap(carpeta), nombre, { carpetaDeArtefactos: carpeta, decodificar: decodificarImagen }),
692
+ cajasDeMaqueta: async (imagen, bytes) => {
693
+ const html = localizadorDeHtml(imagen);
694
+ const medidas = medidasDeImagen(bytes);
695
+ if (html === undefined || medidas === undefined)
696
+ return undefined;
697
+ const { ancho, alto } = viewportDeMaqueta(medidas.ancho, medidas.alto);
698
+ return cajasDeMaqueta(html, ancho, alto);
699
+ },
648
700
  }),
649
701
  // Medir la estructura contra la maqueta: la otra mitad del crítico, con el raíz como él.
650
702
  crearCompararCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)), leerReferencia: lectorDeReferencias }),
@@ -692,6 +744,9 @@ export async function abrirSesionTrueforge(opciones) {
692
744
  ...herramientasDeJuicio(),
693
745
  ...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
694
746
  ...traerDeStitch(),
747
+ ...(opciones.puedeProponerTareas === true
748
+ ? [crearProponerTareas((p) => void propuestasPorAnunciar.push(p))]
749
+ : []),
695
750
  ];
696
751
  const propiasDe = (agente) => [
697
752
  crearBusquedaRegex(backend),
@@ -716,6 +771,14 @@ export async function abrirSesionTrueforge(opciones) {
716
771
  ...(opciones.iconos !== undefined && recibeBuscarIcono(agente)
717
772
  ? [crearBuscarIcono(opciones.iconos)]
718
773
  : []),
774
+ // Las fuentes de la maqueta (Google Fonts) a `fonts/`: al desarrollador y al diseñador (`recibeFuentes`), con el
775
+ // puerto. `traer_fuente` ESCRIBE un binario: `capacidadesDelEspecialista` la pone en `requireApprovalForTools`.
776
+ ...(opciones.fuentes !== undefined && recibeFuentes(agente)
777
+ ? [
778
+ crearBuscarFuente(opciones.fuentes),
779
+ crearTraerFuente(opciones.fuentes, { raiz, perfil: agente }),
780
+ ]
781
+ : []),
719
782
  // Fondos SVG: pura y sin red, sin puerto; a quien escribe el proyecto, como deepagents.
720
783
  ...(recibeGenerarFondo(agente) ? [crearGenerarFondoSvg()] : []),
721
784
  // ¿Cambió la zona tras el toque? Al conductor, que es quien saca las capturas: comprueba una
@@ -886,6 +949,9 @@ export async function abrirSesionTrueforge(opciones) {
886
949
  const hiloDelPadre = params.parent?.thread_id;
887
950
  if (hiloDelPadre !== undefined && hiloDelPadre !== "")
888
951
  padreDeHilo.set(params.threadId, hiloDelPadre);
952
+ const llamadaDelPadre = params.parent?.tool_call_id;
953
+ if (hiloDelPadre === HILO_RAIZ && llamadaDelPadre !== undefined)
954
+ hiloDeLlamadaDelRaiz.set(llamadaDelPadre, params.threadId);
889
955
  nacimientoDeHilo.set(params.threadId, Date.now());
890
956
  quienEs.set(params.threadId, agente?.nombre ?? params.request.name);
891
957
  if (agente !== undefined)
@@ -930,7 +996,7 @@ export async function abrirSesionTrueforge(opciones) {
930
996
  "",
931
997
  nota,
932
998
  anuncioDeSkills(agente, catalogo),
933
- ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined })] : []),
999
+ ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined, conFuentes: opciones.fuentes !== undefined })] : []),
934
1000
  ]
935
1001
  .filter((l) => l !== undefined)
936
1002
  .join("\n")
@@ -1025,7 +1091,7 @@ export async function abrirSesionTrueforge(opciones) {
1025
1091
  hijosConMemoria.clear();
1026
1092
  const definicion = {
1027
1093
  modelClient: llm,
1028
- 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"),
1094
+ 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"),
1029
1095
  // Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
1030
1096
  iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
1031
1097
  };
@@ -1086,6 +1152,51 @@ export async function abrirSesionTrueforge(opciones) {
1086
1152
  * de terminal no lo pasa, la misma regla que el checkpoint de deepagents.
1087
1153
  */
1088
1154
  const persistir = opciones.hilo !== undefined;
1155
+ /**
1156
+ * **La foto A MITAD de turno.** La del `finally` no basta: un corte DURO —el proceso muere, sin
1157
+ * cierre ordenado— no pasa por él, y medido en el incidente que lo motiva, un turno de 45 minutos
1158
+ * se perdió entero, mensaje de la persona incluido, aunque los ficheros sí habían cambiado.
1159
+ * deepagents no lo sufre: su checkpointer guarda cada paso.
1160
+ *
1161
+ * Se guarda en cada añadido del RAÍZ —el primero es el mensaje de la persona— y cada vez que un
1162
+ * hijo completa una escritura, porque lo escrito no toca el contexto del raíz y la foto lo nombra.
1163
+ * `toSnapshot()` es seguro aquí: la librería SUSTITUYE el contexto (`concat`), nunca lo muta, y
1164
+ * guardar es atómico. Las tool calls abiertas se saldan con el texto de una INTERRUPCIÓN
1165
+ * (`textoDeInterrupcion`), no con el de un turno cortado: tras un corte, «vuelve a pedirlo»
1166
+ * duplicaría lo que el especialista ya dejó en el disco. El turno que acaba bien la PISA con la
1167
+ * del `finally`, así que esta foto solo se lee si el proceso murió. Nunca tumba un turno.
1168
+ */
1169
+ let ultimaAMitad;
1170
+ const escritosDeLaRama = (hijo) => {
1171
+ const deLaRama = (h) => {
1172
+ for (let actual = h; actual !== undefined; actual = padreDeHilo.get(actual))
1173
+ if (actual === hijo)
1174
+ return true;
1175
+ return false;
1176
+ };
1177
+ return [...new Set([...escritosDeCadaHilo].filter(([h]) => deLaRama(h)).flatMap(([, cambios]) => [...cambios.keys()]))];
1178
+ };
1179
+ const guardarAMitad = () => {
1180
+ if (!persistir || raizActual === undefined)
1181
+ return;
1182
+ const foto = raizActual.toSnapshot();
1183
+ const escritos = [...escritosDeCadaHilo.values()].reduce((n, cambios) => n + cambios.size, 0);
1184
+ if (ultimaAMitad?.context === foto.context && ultimaAMitad.escritos === escritos)
1185
+ return;
1186
+ ultimaAMitad = { context: foto.context, escritos };
1187
+ const textoDe = (id) => {
1188
+ const hijo = hiloDeLlamadaDelRaiz.get(id);
1189
+ if (hijo === undefined)
1190
+ return textoDeInterrupcion({ escritos: [] });
1191
+ return textoDeInterrupcion({ especialista: especialistaDeHilo.get(hijo) ?? quienEs.get(hijo) ?? hijo, escritos: escritosDeLaRama(hijo) });
1192
+ };
1193
+ try {
1194
+ guardarMemoria(raiz, hilo, { ...foto, context: saldarColgadas(foto.context, undefined, textoDe) });
1195
+ }
1196
+ catch {
1197
+ // Sin la foto de a mitad el turno sigue: lo peor es lo de antes, perderlo si el proceso muere.
1198
+ }
1199
+ };
1089
1200
  /** El aviso de una memoria que existía y no se entendió: sale en el PRIMER turno y se gasta. */
1090
1201
  let avisoDeMemoria;
1091
1202
  /**
@@ -1153,6 +1264,9 @@ export async function abrirSesionTrueforge(opciones) {
1153
1264
  async function* paso(lote, senal, pendientes, preguntas = []) {
1154
1265
  for await (const _ of orquestador.send(lote))
1155
1266
  void _;
1267
+ // Lo que acaba de entrar en el raíz —el mensaje de la persona, una aprobación, una reparación—
1268
+ // ya está a salvo antes de la primera llamada al modelo.
1269
+ guardarAMitad();
1156
1270
  const llamadas = new Map();
1157
1271
  // El razonamiento de los especialistas, juntado por hilo hasta que su mensaje se completa.
1158
1272
  const pensamientos = new Map();
@@ -1161,6 +1275,8 @@ export async function abrirSesionTrueforge(opciones) {
1161
1275
  while (!r.done) {
1162
1276
  const evento = r.value;
1163
1277
  const deHilo = evento.thread_id ?? HILO_RAIZ;
1278
+ if (deHilo === HILO_RAIZ && evento.type === "internal.agent.context.append")
1279
+ guardarAMitad();
1164
1280
  if (evento.type === "internal.agent.context.append" && Array.isArray(evento.output)) {
1165
1281
  for (const m of evento.output) {
1166
1282
  for (const t of m.tool_calls ?? []) {
@@ -1172,8 +1288,9 @@ export async function abrirSesionTrueforge(opciones) {
1172
1288
  args = {};
1173
1289
  }
1174
1290
  llamadas.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
1175
- if (t.function.name === "write_file" || t.function.name === "edit_file")
1291
+ if (t.function.name === "write_file" || t.function.name === "edit_file" || esEscrituraBinaria(t.function.name)) {
1176
1292
  escriturasEnVuelo.set(claveDe(deHilo, t.id), { nombre: t.function.name, args });
1293
+ }
1177
1294
  if (t.function.name === "xone_critica_visual")
1178
1295
  ultimaCriticaDeHilo.set(deHilo, Date.now());
1179
1296
  // La traza: la MISMA lista blanca que el evento (`detalleDe`, `parametrosDe`), nunca
@@ -1201,11 +1318,12 @@ export async function abrirSesionTrueforge(opciones) {
1201
1318
  const escritura = escriturasEnVuelo.get(claveDe(deHilo, evento.tool_call_id));
1202
1319
  if (escritura !== undefined) {
1203
1320
  escriturasEnVuelo.delete(claveDe(deHilo, evento.tool_call_id));
1204
- if (deHilo !== HILO_RAIZ && escrituraConExito(evento.content)) {
1321
+ if (deHilo !== HILO_RAIZ && escrituraConExito(evento.content, escritura.nombre)) {
1205
1322
  const cambios = escritosDeCadaHilo.get(deHilo) ?? new Map();
1206
1323
  anotarEscritura(cambios, escritura.nombre, escritura.args);
1207
1324
  escritosDeCadaHilo.set(deHilo, cambios);
1208
1325
  ultimaEscrituraDeHilo.set(deHilo, Date.now());
1326
+ guardarAMitad();
1209
1327
  }
1210
1328
  }
1211
1329
  }
@@ -1280,8 +1398,10 @@ export async function abrirSesionTrueforge(opciones) {
1280
1398
  yield* eventos;
1281
1399
  while (artefactosPorAnunciar.length > 0)
1282
1400
  yield { tipo: "artefacto", artefacto: artefactosPorAnunciar.shift() };
1401
+ yield* propuestasPendientes();
1283
1402
  r = await it.next();
1284
1403
  }
1404
+ yield* propuestasPendientes();
1285
1405
  const resultado = r.value;
1286
1406
  for (const accion of resultado.required_actions ?? []) {
1287
1407
  // Dos interrupciones distintas, y no se mezclan: una APROBACIÓN decide sobre una escritura