xonecode 0.38.15 → 0.38.31

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 (100) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-C9BEoDWh.js → EditorDeFichero-CT2vAqMI.js} +1 -1
  2. package/apps/web/dist/assets/index-DjZ3hYu-.js +506 -0
  3. package/apps/web/dist/assets/index-oppsOrF4.css +1 -0
  4. package/apps/web/dist/assets/{php-DIdt3Vgn.js → php-B5s63fYs.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-DYEvg0Dr.js → ruby-D9QLqlM3.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/conectores/gestorJira.js +118 -1
  8. package/dist/agent/conectores/gestorJira.js.map +1 -1
  9. package/dist/agent/dispositivos/decodificarImagen.js +8 -0
  10. package/dist/agent/dispositivos/decodificarImagen.js.map +1 -1
  11. package/dist/agent/dispositivos/juezVisual.js +43 -40
  12. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  13. package/dist/agent/grafo/aprendido.js +60 -0
  14. package/dist/agent/grafo/aprendido.js.map +1 -0
  15. package/dist/agent/grafo/criticaVisual.js +17 -5
  16. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  17. package/dist/agent/grafo/xoneAgent.js +2 -0
  18. package/dist/agent/grafo/xoneAgent.js.map +1 -1
  19. package/dist/agent/motores/trueforge/capacidades.js +4 -2
  20. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  21. package/dist/agent/motores/trueforge/convertirAP.js +86 -0
  22. package/dist/agent/motores/trueforge/convertirAP.js.map +1 -0
  23. package/dist/agent/motores/trueforge/sesionTrueforge.js +197 -11
  24. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  25. package/dist/agent/motores/trueforge/traerDeStitch.js +65 -5
  26. package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -1
  27. package/dist/agent/subagentes/agentesEnDisco.js +28 -3
  28. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  29. package/dist/agent/subagentes/escrituraExterna.js +8 -1
  30. package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
  31. package/dist/agent/subagentes/pluginDeSkills.js +31 -6
  32. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -1
  33. package/dist/agent/subagentes/subagenteExterno.js +28 -9
  34. package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
  35. package/dist/agent/tareas/aumentador.js +13 -2
  36. package/dist/agent/tareas/aumentador.js.map +1 -1
  37. package/dist/agent/tareas/juezDeTarea.js +13 -8
  38. package/dist/agent/tareas/juezDeTarea.js.map +1 -1
  39. package/dist/agent/tareas/reflejoEnElGestor.js +120 -0
  40. package/dist/agent/tareas/reflejoEnElGestor.js.map +1 -0
  41. package/dist/agent/tareas/sondeoDelGestor.js +201 -0
  42. package/dist/agent/tareas/sondeoDelGestor.js.map +1 -0
  43. package/dist/agent/turno/resumenDeTool.js +2 -0
  44. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  45. package/dist/agent/turno/turnoReal.js +1 -0
  46. package/dist/agent/turno/turnoReal.js.map +1 -1
  47. package/dist/cli/consola.js.map +1 -1
  48. package/dist/cli/main.js +5 -0
  49. package/dist/cli/main.js.map +1 -1
  50. package/dist/core/agentes.js +87 -2
  51. package/dist/core/agentes.js.map +1 -1
  52. package/dist/core/aprendido.js +68 -0
  53. package/dist/core/aprendido.js.map +1 -0
  54. package/dist/core/comandoExterno.js +29 -0
  55. package/dist/core/comandoExterno.js.map +1 -1
  56. package/dist/core/compararCapturas.js +62 -4
  57. package/dist/core/compararCapturas.js.map +1 -1
  58. package/dist/core/config.js +29 -0
  59. package/dist/core/config.js.map +1 -1
  60. package/dist/core/convertirAP.js +87 -0
  61. package/dist/core/convertirAP.js.map +1 -0
  62. package/dist/core/entrega.js +78 -10
  63. package/dist/core/entrega.js.map +1 -1
  64. package/dist/core/geometriaDePantalla.js +107 -5
  65. package/dist/core/geometriaDePantalla.js.map +1 -1
  66. package/dist/core/gestorDeTareas.js +2 -0
  67. package/dist/core/gestorDeTareas.js.map +1 -1
  68. package/dist/core/idiomaDeLaRespuesta.js +43 -0
  69. package/dist/core/idiomaDeLaRespuesta.js.map +1 -1
  70. package/dist/core/metodoDeComparacionVisual.js +71 -0
  71. package/dist/core/metodoDeComparacionVisual.js.map +1 -0
  72. package/dist/core/notify.js +4 -0
  73. package/dist/core/notify.js.map +1 -1
  74. package/dist/core/nucleoXone.js +42 -0
  75. package/dist/core/nucleoXone.js.map +1 -1
  76. package/dist/core/paqueteDeSoporte.js +2 -0
  77. package/dist/core/paqueteDeSoporte.js.map +1 -1
  78. package/dist/core/ports.js +36 -2
  79. package/dist/core/ports.js.map +1 -1
  80. package/dist/core/stitch.js +43 -0
  81. package/dist/core/stitch.js.map +1 -1
  82. package/dist/core/tareas.js.map +1 -1
  83. package/dist/core/tareasEnElGestor.js +197 -0
  84. package/dist/core/tareasEnElGestor.js.map +1 -0
  85. package/dist/core/textos.js +10 -3
  86. package/dist/core/textos.js.map +1 -1
  87. package/dist/web/servidor/arranque.js +483 -42
  88. package/dist/web/servidor/arranque.js.map +1 -1
  89. package/dist/web/servidor/consolaDeTarea.js +5 -6
  90. package/dist/web/servidor/consolaDeTarea.js.map +1 -1
  91. package/dist/web/servidor/corredorDeTareas.js +33 -4
  92. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  93. package/dist/web/servidor/transporte.js +11 -0
  94. package/dist/web/servidor/transporte.js.map +1 -1
  95. package/package.json +2 -2
  96. package/skills/xone-development/lib/aUtf8.mjs +68 -0
  97. package/skills/xone-development/references/css/selectores-unidades-colores.md +2 -0
  98. package/skills/xone-development/scripts/xone-a-utf8 +60 -0
  99. package/apps/web/dist/assets/index-BpDt_1vN.js +0 -506
  100. package/apps/web/dist/assets/index-D_Z-V9n1.css +0 -1
@@ -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, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
28
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, 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";
@@ -37,7 +37,7 @@ import { sumarPorModelo } from "../../../core/actos.js";
37
37
  import { ColaDeEventos, entrelazar } from "../../../core/entrelazar.js";
38
38
  import { modeloExternoParaTrueforge } from "./modeloExterno.js";
39
39
  import { diferenciasDelContraste, metricasDeTrueforge } from "./metricasTrueforge.js";
40
- import { respuestaEnChinoSinPedirlo, textoDeReescritura } from "../../../core/idiomaDeLaRespuesta.js";
40
+ import { respuestaEnChinoSinPedirlo, TOPE_DE_RECORDATORIOS_DE_IDIOMA, textoDeReescritura, textoDeRecordatorioDeIdioma, textoEnInglesSinPedirlo, } from "../../../core/idiomaDeLaRespuesta.js";
41
41
  import { cambiosQueSeVerifican, huellaDeErrores, repartirHallazgos, textoDeReparacion, tocaCriticarPantalla, TOPE_REPARACIONES } from "../../turno/verificacion.js";
42
42
  import { correrTurno } from "../../../core/turno.js";
43
43
  import { createTokenTracker } from "../../../vendor/tokenTracking.js";
@@ -45,7 +45,9 @@ import { MAX_APPROVAL_ROUNDS } from "../../../vendor/hitl.js";
45
45
  import { artefactosNuevos, backendDeAgente, entornoDeLaShellDelProyecto, fotoDeArtefactos, lectorDelDispositivoDeSesion, scriptsDeLasSkills } from "../../grafo/proyecto.js";
46
46
  import { carpetaDeHotswap } from "../../../core/hotswap.js";
47
47
  import { cargarAgentes } from "../../subagentes/agentesEnDisco.js";
48
- import { fichaDeAgente, promptDeAgente, recibeMarcarCriterios, repartirSkills } from "../../../core/agentes.js";
48
+ import { bloqueDeAprendido, NOMBRE_APUNTAR_APRENDIZAJE, USO_DE_APRENDIDO_EXTERNO, USO_DE_APUNTAR_APRENDIZAJE } from "../../../core/aprendido.js";
49
+ import { crearApuntarAprendizaje, leerAprendido } from "../../grafo/aprendido.js";
50
+ import { fichaDeAgente, promptDeAgente, recibeMarcarCriterios, recibeNucleoDeTrabajo, repartirSkills, skillsConLasDeQuienLlama, textoDeCarpetasDeLaSesion, textoDeTrabajoSinLlamar } from "../../../core/agentes.js";
49
51
  import { PERFIL_DEL_ORQUESTADOR, promptOrquestador } from "../../grafo/xoneAgent.js";
50
52
  import { permisosDe, seDetieneEn, TEXTO_HITL } from "../../grafo/perfiles.js";
51
53
  import { cambioDe } from "../../turno/interrupts.js";
@@ -65,6 +67,7 @@ import { avisoDeReparto } from "../../../core/repartoDeEncargo.js";
65
67
  import { leerTareasDelPlan, RUTA_DE_TASKS } from "../../../core/tareasDelPlan.js";
66
68
  import { esDeLaMaqueta } from "../../../core/stitch.js";
67
69
  import { recibeConectores } from "../../../core/conectores.js";
70
+ import { crearConvertirAP, NOMBRE_CONVERTIR_A_P, recibeConvertirAP } from "./convertirAP.js";
68
71
  import { buscarMaqueta, medirContraMaqueta, resumenDeMedida, textoDeMedidaAutomatica, ultimaCaptura } from "./medidaAutomatica.js";
69
72
  import { anotarEscritura, capacidadDeInformesDeHijos, esEscrituraBinaria, escrituraConExito, textoDelInforme } from "./informesDeHijos.js";
70
73
  import { crearDiagnosticoDeTools } from "../../turno/diagnosticoDeTools.js";
@@ -72,7 +75,8 @@ import { encenderTrazaDeErrores } from "../../trazaDeErroresEnDisco.js";
72
75
  import { anotarPaso } from "../../../core/trazaDeErrores.js";
73
76
  import { entornoConDepuracion } from "../../turno/depuracion.js";
74
77
  import { detalleDe, parametrosDe } from "../../turno/resumenDeTool.js";
75
- import { lineaDeComprobacion, soloDocumentacion, TOPE_DE_COMPROBACIONES_AL_JUEZ } from "../../../core/entrega.js";
78
+ import { lineaDeComprobacion, omisionExplicada, soloDocumentacion, TOPE_DE_COMPROBACIONES_AL_JUEZ } from "../../../core/entrega.js";
79
+ import { bloqueantesDelInforme } from "../../../core/geometriaDePantalla.js";
76
80
  import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, saldarColgadas, textoDeInterrupcion, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
77
81
  import { crearNota, sobrantes } from "./notas.js";
78
82
  import { crearMemoriaDeEspecialistas } from "./memoriaDeEspecialistas.js";
@@ -126,6 +130,22 @@ const BUCLE_DE_CALIDAD = [
126
130
  "repite. Para cuando la distancia estructural baje del 10 % en vertical y en horizontal y el crítico no señale diferencias de forma con el diseño, o tras TRES vueltas, y",
127
131
  "di lo que sigue faltando en vez de darlo por hecho.",
128
132
  ].join("\n");
133
+ /**
134
+ * Cuando quien MAQUETA es un hijo de Claude Code con ejecución, el bucle lo hace ÉL: despliega, captura, compara con la
135
+ * maqueta y corrige (`textoDeTrabajoSinLlamar`). Medido en Maset: el orquestador, con `BUCLE_DE_CALIDAD`, volvía a pasar el
136
+ * crítico y le encargaba otra vuelta —y otra al diseñador por un SVG—, y cada vuelta era un Claude Code NUEVO que releía
137
+ * todo: unos quince minutos cada una, con las piezas ya bien. Dos bucles uno encima de otro.
138
+ */
139
+ export function textoDeMaquetadorExterno(nombres) {
140
+ return [
141
+ `${nombres.join(", ")} corre en Claude Code con su shell: despliega, captura, COMPARA con la maqueta y CORRIGE él mismo hasta`,
142
+ "cumplir. Con él: dale en UN encargo el diseño, la tabla de recursos y el criterio; a su vuelta pasa el crítico UNA vez, y solo",
143
+ "vuelve a encargarle si queda algo BLOQUEANTE (una pieza que falta, algo tapado o fuera de pantalla) o que él no comprobó. Las",
144
+ "diferencias de detalle van en tu resumen final, no en otra vuelta: cada vuelta suya empieza de cero. Si un recurso del",
145
+ "diseñador está mal (proporción, color, un icono equivocado), díselo a él y que lo corrija en esa misma vuelta: puede escribir en",
146
+ "`icons/`, y otra ronda diseñador → desarrollador cuesta dos arranques.",
147
+ ].join("\n");
148
+ }
129
149
  /**
130
150
  * Lo que el orquestador tiene que saber para DELEGAR en ESTE motor. Su prompt es el de siempre
131
151
  * (`promptOrquestador`), que habla de `task` y de la ficha que va en su descripción: aquí la
@@ -161,6 +181,41 @@ export const SIN_HABLAR_DEL_HARNESS = "CON QUIÉN HABLAS: con un desarrollador d
161
181
  "DE ANÁLISIS…), la foto o el contexto que te llega al empezar, ni lo que vas a medir o no: cuéntale lo que encontraste, " +
162
182
  "lo que hiciste en su proyecto y lo que necesitas de él. Si pregunta cómo trabajas, entonces sí.";
163
183
  /** Lo que recibe el agente en modo autónomo cuando pregunta sin marcar ninguna opción como recomendada. */
184
+ /**
185
+ * Al raíz de una TAREA DE FONDO (`trabajoSinPersona`). Preguntar no está prohibido —la pregunta se contesta sola con su
186
+ * recomendada, y pasado el tope aparca la tarea—, pero cada una es una parada: se le pide decidir antes. Y como nadie
187
+ * ha visto el trabajo, la respuesta final es lo que la persona leerá: un resumen con lo que decidió sin preguntar.
188
+ */
189
+ export const TRABAJO_SIN_PERSONA = [
190
+ "TAREA DE FONDO: trabajas SIN nadie delante; la persona leerá el resultado cuando acabes.",
191
+ "- Resuelve tú las dudas: elige la opción más razonable con lo que dicen el encargo, el plan y el proyecto, y sigue.",
192
+ " Pregunta solo si sin la respuesta no puedes hacer nada útil; si preguntas, marca tu opción recomendada con",
193
+ " `(Recommended)`: se contestará sola con ella.",
194
+ "- Tu RESPUESTA FINAL es el resumen de la tarea, en tres apartados: lo que cambiaste (cada fichero y para qué), las",
195
+ " DECISIONES que tomaste tú sin preguntar (cada duda, lo que elegiste y por qué) y lo que no pudiste comprobar o",
196
+ " queda pendiente.",
197
+ ].join("\n");
198
+ /**
199
+ * Lo aprendido, para el ORQUESTADOR: lo que hay apuntado y cuándo apuntar él. Le toca sobre todo lo que le cuente un
200
+ * hijo de motor externo en su apartado «Aprendido:» (ese hijo no tiene la tool).
201
+ */
202
+ export function textoDeAprendidoDelRaiz(lista) {
203
+ return [
204
+ bloqueDeAprendido(lista),
205
+ `${USO_DE_APUNTAR_APRENDIZAJE} Y si un especialista te devuelve un apartado «Aprendido:», apúntalo tú.`,
206
+ ]
207
+ .filter((l) => l !== undefined)
208
+ .join("\n");
209
+ }
210
+ /**
211
+ * La respuesta a una pregunta del agente cuando nadie la contesta y tenía opción recomendada. No es la opción a secas:
212
+ * medido en Maset, con solo «Opción A (Recommended)» el agente cerraba diciendo «lo decidiste tú», como si la hubiera
213
+ * elegido la persona. Es SU recomendación aplicada, y así tiene que contarla.
214
+ */
215
+ export function respuestaAutonomaRecomendada(elegida) {
216
+ return (`Nadie ha contestado (modo autónomo): se aplica TU opción recomendada, «${elegida}». No la eligió el usuario: ` +
217
+ "cuéntala en tu respuesta final entre las decisiones que tomaste tú sin preguntar.");
218
+ }
164
219
  export const RESPUESTA_AUTONOMA_SIN_RECOMENDADA = "Modo autónomo: no hay nadie a quien preguntar. Decide tú con lo que dicen el encargo y el plan, sigue, y di en tu respuesta final qué elegiste y por qué.";
165
220
  /** Un comando que saca una captura de pantalla del aparato. */
166
221
  export const COMANDO_DE_CAPTURA = /xone-captura-android|xone-hotswap\s+shot\b|\bscreencap\b/;
@@ -328,6 +383,11 @@ export function notaDeDelegacion(agentes, opciones = {}) {
328
383
  "entiende `create_sub_agent` y `name`. Las fichas de los especialistas:",
329
384
  ...agentes.map((a) => `- ${a.nombre}: ${fichaDeAgente(a, opciones)}`),
330
385
  ...(opciones.conComparacion === true ? ["", BUCLE_DE_CALIDAD] : []),
386
+ // Si quien maqueta corre en Claude Code con su shell, el bucle es SUYO: el orquestador no lo repite encima.
387
+ ...(() => {
388
+ const externos = agentes.filter((a) => a.motor === "claude-code" && a.ejecucion === true && !a.soloLectura).map((a) => a.nombre);
389
+ return opciones.conComparacion === true && externos.length > 0 ? ["", textoDeMaquetadorExterno(externos)] : [];
390
+ })(),
331
391
  ...(opciones.conEsperas === true
332
392
  ? [
333
393
  "",
@@ -380,6 +440,13 @@ const LIMITES_DE = {
380
440
  */
381
441
  export const USO_DE_XONE_ATRIBUTOS = "Para saber si un atributo XML existe en un nodo (coll, group, frame, prop) y qué hace, pregunta PRIMERO a `xone_atributos`: " +
382
442
  "contesta en una línea desde las tablas de la skill. Lee la referencia de la skill solo si no lo encuentra, o si necesitas ejemplos o matices.";
443
+ /**
444
+ * CUÁNDO convertir las medidas de una maqueta, por lo mismo que `USO_DE_XONE_ATRIBUTOS`: una tool nombrada solo en la
445
+ * lista no se usa. Medido en Maset: con la regla en la skill, el desarrollador copió los px de la maqueta como `p` y los
446
+ * iconos salieron unas 2,6 veces más pequeños.
447
+ */
448
+ export const USO_DE_CONVERTIR_A_P = "Los px de una maqueta (su code.html, su imagen) NO son p: toda medida que copies de ella —anchos, altos, márgenes, " +
449
+ "paddings, tamaño de iconos— pasa ANTES por `convertir_a_p`, todas de una vez, y escribes lo que devuelve.";
383
450
  export function notaDeTools(nombres, limite) {
384
451
  return `NOTA DEL HARNESS: aunque tu identidad diga lo contrario, NO tienes las mismas tools que quien te delega. Tienes ${nombres
385
452
  .map((n) => `\`${n}\``)
@@ -437,6 +504,25 @@ function preguntaDeLaFoto(foto) {
437
504
  };
438
505
  }
439
506
  const claveDe = (hilo, id) => `${hilo}:${id}`;
507
+ /**
508
+ * ¿Cambió algún fichero del proyecto (fuera de `.xonecode/`) después de `desde`? Por la hora de modificación del disco,
509
+ * que es lo que vale para los dos motores: un hijo de Claude Code escribe con sus tools y no pasa por las nuestras. Lo
510
+ * que no se puede mirar no cuenta como cambio.
511
+ */
512
+ export function cambioDespuesDe(raiz, desde) {
513
+ for (const virtual of ficherosDelProyecto(raiz)) {
514
+ if (virtual.startsWith("/.xonecode/"))
515
+ continue;
516
+ try {
517
+ if (statSync(join(raiz, virtual)).mtimeMs > desde)
518
+ return true;
519
+ }
520
+ catch {
521
+ // Borrado entre medias, o sin permiso: no se afirma nada.
522
+ }
523
+ }
524
+ return false;
525
+ }
440
526
  /**
441
527
  * `agregarNota` no vive todavía en `SesionReal` (IXCODE-4, Task 6 se la añade ahí como opcional,
442
528
  * compartida con deepagents): se ensancha el tipo de retorno AQUÍ, en vez de esperar a esa task,
@@ -554,6 +640,9 @@ export async function abrirSesionTrueforge(opciones) {
554
640
  let capturasDelTurno = [];
555
641
  /** Lo que dieron las comprobaciones visuales del turno, una línea cada una, para el juez de QA (`ResultadoDeTurno`). */
556
642
  let comprobacionesDelTurno = [];
643
+ let bloqueantesVisualesDelTurno = [];
644
+ // Cuándo llegó esa crítica: si el proyecto cambia DESPUÉS, sus bloqueantes ya no son la pantalla final (`cambioDespuesDe`).
645
+ let horaDeLaUltimaCritica;
557
646
  const anotarComprobacion = (linea) => {
558
647
  comprobacionesDelTurno = [...comprobacionesDelTurno, linea].slice(-TOPE_DE_COMPROBACIONES_AL_JUEZ);
559
648
  };
@@ -577,6 +666,30 @@ export async function abrirSesionTrueforge(opciones) {
577
666
  const detencion = crearControlDeDetencion(HILO_RAIZ);
578
667
  /** Tool calls tiradas por DETENER en la sesión: si se queda en cero, el filtro es solo red. */
579
668
  let llamadasTiradasPorDetener = 0;
669
+ /**
670
+ * **El recordatorio del idioma**, para el raíz que escribe en INGLÉS entre herramientas (`textoEnInglesSinPedirlo`,
671
+ * medido en Maset). Lo pone el turno al cerrarse un mensaje suyo, y entra como mensaje de usuario antes de su siguiente
672
+ * llamada —la del raíz, no la de un hijo—; como mucho `TOPE_DE_RECORDATORIOS_DE_IDIOMA` por turno.
673
+ */
674
+ let recordatorioDeIdioma;
675
+ let recordatoriosEnElTurno = 0;
676
+ const capacidadDeIdioma = {
677
+ nombre: "idioma",
678
+ tools: [],
679
+ capability: {
680
+ preLLMProcessors: [
681
+ {
682
+ async *processPreLLM() {
683
+ const texto = recordatorioDeIdioma;
684
+ if (texto === undefined)
685
+ return;
686
+ recordatorioDeIdioma = undefined;
687
+ yield { type: "internal.agent.context.append", context: [{ role: "user", content: texto }], output: [] };
688
+ },
689
+ },
690
+ ],
691
+ },
692
+ };
580
693
  const capacidadDeNotasDeLaSesion = capacidadDeNotas(notas, {
581
694
  detencion,
582
695
  nombreDe: (h) => quienEs.get(h) ?? h,
@@ -783,6 +896,8 @@ export async function abrirSesionTrueforge(opciones) {
783
896
  ...herramientasDeJuicio(),
784
897
  ...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
785
898
  ...traerDeStitch(),
899
+ // Lo aprendido: el orquestador también apunta, sobre todo lo que le cuenta un hijo de motor externo (que no tiene la tool).
900
+ crearApuntarAprendizaje({ raiz, quien: "orquestador" }),
786
901
  ...(opciones.puedeProponerTareas === true
787
902
  ? [crearProponerTareas((p) => void propuestasPorAnunciar.push(p))]
788
903
  : []),
@@ -820,11 +935,23 @@ export async function abrirSesionTrueforge(opciones) {
820
935
  : []),
821
936
  // Fondos SVG: pura y sin red, sin puerto; a quien escribe el proyecto, como deepagents.
822
937
  ...(recibeGenerarFondo(agente) ? [crearGenerarFondoSvg()] : []),
938
+ // Las medidas de una maqueta a `p`: pura, a quien escribe el proyecto. La resolución la lee del app.xml de la raíz.
939
+ ...(recibeConvertirAP(agente)
940
+ ? [
941
+ crearConvertirAP({
942
+ raiz,
943
+ ...(carpeta === undefined ? {} : { artefactos: carpeta }),
944
+ ...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
945
+ }),
946
+ ]
947
+ : []),
823
948
  // ¿Cambió la zona tras el toque? Al conductor, que es quien saca las capturas: comprueba una
824
949
  // ACCIÓN suya, no juzga la pantalla (eso sigue siendo del crítico y de `comparar_capturas`).
825
950
  ...(carpeta !== undefined && agente.ejecucion === true
826
951
  ? [crearDiferenciaDeCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)) })]
827
952
  : []),
953
+ // Lo aprendido del proyecto y del aparato, para los encargos siguientes: a quien trabaja (`recibeNucleoDeTrabajo`).
954
+ ...(recibeNucleoDeTrabajo(agente) ? [crearApuntarAprendizaje({ raiz, quien: agente.nombre })] : []),
828
955
  // Y marcar en el plan lo comprobado, con el reparto de deepagents (`recibeMarcarCriterios`).
829
956
  ...(recibeMarcarCriterios(agente) ? [crearMarcarCriteriosDelPlan({ raiz })] : []),
830
957
  // El BUCLE del desarrollador: quien puede llamar al de pruebas también tiene el crítico y la medida, para
@@ -890,13 +1017,18 @@ export async function abrirSesionTrueforge(opciones) {
890
1017
  * su nombre; el entorno de NUESTRA shell; y la lectura de ESTAS dos carpetas de la sesión, que
891
1018
  * es donde sus scripts dejan las capturas. Codex y OpenCode siguen sin shell.
892
1019
  */
893
- const ejecucion = agente.ejecucion === true && motor === "claude-code" && carpeta !== undefined
1020
+ const conEjecucion = agente.ejecucion === true && motor === "claude-code" && carpeta !== undefined;
1021
+ // Con ejecución, también las skills de a quien llamaría: no puede llamarlo, así que lo hace él (`skillsConLasDeQuienLlama`).
1022
+ const delHijo = conEjecucion ? { ...agente, skills: skillsConLasDeQuienLlama(agente, especialistas()) } : agente;
1023
+ const ejecucion = conEjecucion
894
1024
  ? {
895
- scripts: scriptsDeLasSkills(raiz, agente.skills),
1025
+ scripts: scriptsDeLasSkills(raiz, delHijo.skills),
896
1026
  entorno: entornoDeLaShellDelProyecto(raiz, carpeta),
897
1027
  lecturas: [carpeta, carpetaDeHotswap(carpeta)],
898
1028
  }
899
1029
  : undefined;
1030
+ // Las carpetas de la sesión se LEEN aunque no ejecute: es donde el orquestador le deja la maqueta y las capturas.
1031
+ const lecturasDeSesion = motor === "claude-code" && carpeta !== undefined ? [carpeta, carpetaDeHotswap(carpeta)] : undefined;
900
1032
  /**
901
1033
  * Lo que sus comandos dejen en `/artefactos/` se ANUNCIA al volver, con la MISMA foto que nuestra
902
1034
  * shell (`fotoDeArtefactos`): por nuestra shell se anunciaba y por la suya no, y una captura
@@ -929,12 +1061,23 @@ export async function abrirSesionTrueforge(opciones) {
929
1061
  peticion: () => ({
930
1062
  motor,
931
1063
  cwd: raiz,
932
- instrucciones: `${promptDeAgente(agente, repartirSkills(agente, disponibles))}\n\n${inventarioDelProyecto(ficherosDelProyecto(raiz))}`,
1064
+ instrucciones: [
1065
+ promptDeAgente(delHijo, repartirSkills(delHijo, disponibles)),
1066
+ ...(conEjecucion && (agente.llama ?? []).length > 0 ? [textoDeTrabajoSinLlamar(agente.llama ?? [], carpeta)] : []),
1067
+ ...(lecturasDeSesion === undefined ? [] : [textoDeCarpetasDeLaSesion(lecturasDeSesion[0], lecturasDeSesion[1])]),
1068
+ ...[bloqueDeAprendido(leerAprendido(raiz))].filter((b) => b !== undefined),
1069
+ USO_DE_APRENDIDO_EXTERNO,
1070
+ inventarioDelProyecto(ficherosDelProyecto(raiz)),
1071
+ ].join("\n\n"),
933
1072
  tarea: params.request.input,
934
1073
  ...(agente.modelo === undefined ? {} : { modelo: agente.modelo }),
935
1074
  permitirEscritura: !agente.soloLectura,
936
1075
  agente: agente.nombre,
1076
+ skills: repartirSkills(delHijo, disponibles).suyas,
937
1077
  ...(ejecucion === undefined ? {} : { ejecucion }),
1078
+ ...(lecturasDeSesion === undefined ? {} : { lecturas: lecturasDeSesion }),
1079
+ // El esfuerzo de su `.md`, que Claude Code aplica como `effort`; los otros dos no tienen dónde recibirlo.
1080
+ ...(motor === "claude-code" && agente.esfuerzo !== undefined ? { esfuerzo: agente.esfuerzo } : {}),
938
1081
  }),
939
1082
  senal: () => aborto?.signal,
940
1083
  }),
@@ -1006,6 +1149,8 @@ export async function abrirSesionTrueforge(opciones) {
1006
1149
  notas: capacidadDeNotasDeLaSesion,
1007
1150
  puedeLlamar: (a) => conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0,
1008
1151
  informes: capacidadDeInformes,
1152
+ // Una tarea de fondo no tiene a quién preguntar: sus escrituras locales no esperan al control de la librería.
1153
+ ...(opciones.trabajoSinPersona === true ? { escribeSinPreguntar: true } : {}),
1009
1154
  vueltas: (a) => a.ejecucion === true
1010
1155
  ? capacidadDeAvisoDeVueltas({ vueltasDe: (hilo) => capturasDeCadaHilo.get(hilo) ?? 0, umbrales: UMBRALES_DE_CAPTURAS, texto: textoDeCapturas })
1011
1156
  : conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0
@@ -1025,7 +1170,11 @@ export async function abrirSesionTrueforge(opciones) {
1025
1170
  const nota = [
1026
1171
  notaDeTools(nombres, agente === undefined ? LIMITE_DEL_GENERICO : LIMITES_DE[clase]),
1027
1172
  ...(nombres.includes(NOMBRE_ATRIBUTOS_XONE) ? [USO_DE_XONE_ATRIBUTOS] : []),
1173
+ ...(nombres.includes(NOMBRE_CONVERTIR_A_P) ? [USO_DE_CONVERTIR_A_P] : []),
1174
+ ...(nombres.includes(NOMBRE_APUNTAR_APRENDIZAJE) ? [USO_DE_APUNTAR_APRENDIZAJE] : []),
1028
1175
  ].join("\n");
1176
+ // Lo aprendido en encargos anteriores, leído AHORA (otro encargo pudo apuntar algo hace un momento).
1177
+ const aprendido = bloqueDeAprendido(leerAprendido(raiz));
1029
1178
  const instrucciones = agente === undefined
1030
1179
  ? `${IDIOMA_DEL_ESPECIALISTA}\n\n${nota} Contesta con lo que encuentres y dónde.`
1031
1180
  : [
@@ -1035,6 +1184,7 @@ export async function abrirSesionTrueforge(opciones) {
1035
1184
  "",
1036
1185
  nota,
1037
1186
  anuncioDeSkills(agente, catalogo),
1187
+ ...(aprendido === undefined ? [] : ["", aprendido]),
1038
1188
  ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined, conFuentes: opciones.fuentes !== undefined })] : []),
1039
1189
  ]
1040
1190
  .filter((l) => l !== undefined)
@@ -1130,7 +1280,7 @@ export async function abrirSesionTrueforge(opciones) {
1130
1280
  hijosConMemoria.clear();
1131
1281
  const definicion = {
1132
1282
  modelClient: llm,
1133
- 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"),
1283
+ 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 : "", opciones.trabajoSinPersona === true ? TRABAJO_SIN_PERSONA : "", textoDeAprendidoDelRaiz(leerAprendido(raiz))].filter((l) => l !== "").join("\n\n"),
1134
1284
  // Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
1135
1285
  iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
1136
1286
  };
@@ -1149,6 +1299,7 @@ export async function abrirSesionTrueforge(opciones) {
1149
1299
  capacidadDeFecha(),
1150
1300
  capacidadDeNotasDeLaSesion,
1151
1301
  capacidadDeInformes,
1302
+ capacidadDeIdioma,
1152
1303
  ]),
1153
1304
  /**
1154
1305
  * **La conversación se RESUME a un umbral que depende de la ventana del modelo**
@@ -1354,6 +1505,12 @@ export async function abrirSesionTrueforge(opciones) {
1354
1505
  // Las comprobaciones VISUALES, para el juez: lo que dijeron, medido aquí y no contado por el agente.
1355
1506
  if (llamada !== undefined && (llamada.nombre === NOMBRE_COMPARAR_CAPTURAS || llamada.nombre === "xone_critica_visual") && typeof evento.content === "string") {
1356
1507
  anotarComprobacion(lineaDeComprobacion(llamada.nombre, detalleDe(llamada.nombre, llamada.args), evento.content));
1508
+ // Lo BLOQUEANTE de la ÚLTIMA crítica: es el estado final de la pantalla, y el cierre de una tarea lo pone en
1509
+ // su `## CRITICAL` (`bloqueantesDelInforme`).
1510
+ if (llamada.nombre === "xone_critica_visual") {
1511
+ bloqueantesVisualesDelTurno = bloqueantesDelInforme(evento.content);
1512
+ horaDeLaUltimaCritica = Date.now();
1513
+ }
1357
1514
  }
1358
1515
  const chars = typeof evento.content === "string" ? evento.content.length : 0;
1359
1516
  diagnostico?.resultado?.(llamada?.nombre, llamada === undefined ? undefined : detalleDe(llamada.nombre, llamada.args), chars);
@@ -1476,6 +1633,8 @@ export async function abrirSesionTrueforge(opciones) {
1476
1633
  throw new Error("la sesión está cerrada");
1477
1634
  const t0 = Date.now();
1478
1635
  cancelado = false;
1636
+ recordatoriosEnElTurno = 0;
1637
+ recordatorioDeIdioma = undefined;
1479
1638
  detencion.reiniciar();
1480
1639
  const instantanea = await tomarInstantanea(raiz, opciones.entorno.git);
1481
1640
  const tope = opciones.topeDeRondas ?? MAX_APPROVAL_ROUNDS;
@@ -1486,6 +1645,8 @@ export async function abrirSesionTrueforge(opciones) {
1486
1645
  externosDelTurno = 0;
1487
1646
  capturasDelTurno = [];
1488
1647
  comprobacionesDelTurno = [];
1648
+ bloqueantesVisualesDelTurno = [];
1649
+ horaDeLaUltimaCritica = undefined;
1489
1650
  /** El encargo de ESTE turno tal cual se pidió, y el mismo con la última pregunta y su
1490
1651
  * respuesta al lado, que es lo que se juzga y se repara (ver `flujo`). Ausente = no consta. */
1491
1652
  let encargoDelTurno;
@@ -1531,7 +1692,7 @@ export async function abrirSesionTrueforge(opciones) {
1531
1692
  * recomendada, la otra no— y no se contestó ninguna, se enseñó solo la primera y la segunda se perdió. Con
1532
1693
  * tope, para que no dé vueltas.
1533
1694
  */
1534
- if (preguntas.length > 0 && opciones.sinAprobacion?.() === true && preguntasSolas + preguntas.length <= TOPE_DE_PREGUNTAS_CONTESTADAS_SOLAS) {
1695
+ if (preguntas.length > 0 && (opciones.trabajoSinPersona === true || opciones.sinAprobacion?.() === true) && preguntasSolas + preguntas.length <= TOPE_DE_PREGUNTAS_CONTESTADAS_SOLAS) {
1535
1696
  preguntasSolas += preguntas.length;
1536
1697
  const respuestas = [];
1537
1698
  for (const q of preguntas) {
@@ -1543,7 +1704,7 @@ export async function abrirSesionTrueforge(opciones) {
1543
1704
  type: "user.tool_response",
1544
1705
  thread_id: q.hilo,
1545
1706
  tool_call_id: q.id,
1546
- content: elegida ?? RESPUESTA_AUTONOMA_SIN_RECOMENDADA,
1707
+ content: elegida === undefined ? RESPUESTA_AUTONOMA_SIN_RECOMENDADA : respuestaAutonomaRecomendada(elegida),
1547
1708
  });
1548
1709
  }
1549
1710
  lote = respuestas;
@@ -1719,8 +1880,17 @@ export async function abrirSesionTrueforge(opciones) {
1719
1880
  mensajeCerrado = false;
1720
1881
  ultimoMensaje += evento.texto;
1721
1882
  }
1722
- else if (evento.tipo !== "razonamiento")
1883
+ else if (evento.tipo !== "razonamiento") {
1884
+ // Un mensaje del raíz se CIERRA aquí: si salió en inglés y el usuario no escribe en inglés, su siguiente
1885
+ // llamada lleva el recordatorio (`capacidadDeIdioma`). Sin reescribir lo dicho.
1886
+ if (!mensajeCerrado &&
1887
+ recordatoriosEnElTurno < TOPE_DE_RECORDATORIOS_DE_IDIOMA &&
1888
+ textoEnInglesSinPedirlo(ultimoMensaje, encargoDelTurno ?? peticion)) {
1889
+ recordatoriosEnElTurno += 1;
1890
+ recordatorioDeIdioma = textoDeRecordatorioDeIdioma(encargoDelTurno ?? peticion);
1891
+ }
1723
1892
  mensajeCerrado = true;
1893
+ }
1724
1894
  yield evento;
1725
1895
  }
1726
1896
  /**
@@ -1811,6 +1981,9 @@ export async function abrirSesionTrueforge(opciones) {
1811
1981
  */
1812
1982
  let observacionesVisuales = [];
1813
1983
  if (carpeta !== undefined &&
1984
+ // Si el turno ya pasó `xone_critica_visual` (con la maqueta), este, que mira la captura SOLA, sobra y la
1985
+ // contradice: medido en Maset, tomó por defecto la franja granate con su punto verde, que ES del diseño.
1986
+ horaDeLaUltimaCritica === undefined &&
1814
1987
  tocaCriticarPantalla({ hayCritico: opciones.criticaVisual !== undefined, capturas: capturasDelTurno.length, yaDisparo: visualYaDisparo, intento })) {
1815
1988
  visualYaDisparo = true;
1816
1989
  const captura = capturasDelTurno[capturasDelTurno.length - 1];
@@ -2000,7 +2173,20 @@ export async function abrirSesionTrueforge(opciones) {
2000
2173
  verificador: veredicto,
2001
2174
  pendientes: sinResolver,
2002
2175
  ...(cortadoPorTope ? { topeAgotado: tope } : {}),
2176
+ // La pregunta que deja ESTE turno: la de un turno anterior se consume al empezar (`enEspera`).
2177
+ ...(preguntaEnEspera === undefined ? {} : { preguntaPendiente: consultaDe(preguntaEnEspera.args).pregunta }),
2003
2178
  ...(comprobacionesDelTurno.length === 0 ? {} : { comprobacionesVisuales: comprobacionesDelTurno }),
2179
+ // Lo que su respuesta final EXPLICA (lo nombra) no es crítico: va aparte (`omisionExplicada`).
2180
+ ...(() => {
2181
+ const explicadas = bloqueantesVisualesDelTurno.filter((b) => omisionExplicada(b, respuestaDeLaPasada));
2182
+ const sinExplicar = bloqueantesVisualesDelTurno.filter((b) => !explicadas.includes(b));
2183
+ // Si el proyecto cambió DESPUÉS de esa crítica, lo que vio ya no es la pantalla final: no es crítico.
2184
+ const desfasada = sinExplicar.length > 0 && horaDeLaUltimaCritica !== undefined && cambioDespuesDe(raiz, horaDeLaUltimaCritica);
2185
+ return {
2186
+ ...(sinExplicar.length === 0 ? {} : desfasada ? { bloqueantesDesfasados: sinExplicar } : { bloqueantesVisuales: sinExplicar }),
2187
+ ...(explicadas.length === 0 ? {} : { omisionesExplicadas: explicadas }),
2188
+ };
2189
+ })(),
2004
2190
  ...(hallazgosDelTurno.length === 0 ? {} : { hallazgos: hallazgosDelTurno }),
2005
2191
  ...(preexistentesDelTurno === undefined ? {} : { preexistentes: preexistentesDelTurno }),
2006
2192
  ...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),