xonecode 0.38.20 → 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 (75) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-DySw4j79.js → EditorDeFichero-CT2vAqMI.js} +1 -1
  2. package/apps/web/dist/assets/{index-vvfKjRva.js → index-DjZ3hYu-.js} +77 -77
  3. package/apps/web/dist/assets/{index-D7OcbgXE.css → index-oppsOrF4.css} +1 -1
  4. package/apps/web/dist/assets/{php-CSyDZ6m1.js → php-B5s63fYs.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-3bX3v6o_.js → ruby-D9QLqlM3.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/dispositivos/juezVisual.js +43 -40
  8. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  9. package/dist/agent/grafo/aprendido.js +60 -0
  10. package/dist/agent/grafo/aprendido.js.map +1 -0
  11. package/dist/agent/grafo/criticaVisual.js +17 -5
  12. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  13. package/dist/agent/grafo/xoneAgent.js +2 -0
  14. package/dist/agent/grafo/xoneAgent.js.map +1 -1
  15. package/dist/agent/motores/trueforge/capacidades.js +4 -2
  16. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  17. package/dist/agent/motores/trueforge/sesionTrueforge.js +161 -10
  18. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  19. package/dist/agent/motores/trueforge/traerDeStitch.js +65 -5
  20. package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -1
  21. package/dist/agent/subagentes/escrituraExterna.js +3 -3
  22. package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
  23. package/dist/agent/subagentes/pluginDeSkills.js +10 -0
  24. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -1
  25. package/dist/agent/subagentes/subagenteExterno.js +22 -8
  26. package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
  27. package/dist/agent/tareas/aumentador.js +13 -2
  28. package/dist/agent/tareas/aumentador.js.map +1 -1
  29. package/dist/agent/tareas/juezDeTarea.js +13 -8
  30. package/dist/agent/tareas/juezDeTarea.js.map +1 -1
  31. package/dist/agent/turno/resumenDeTool.js +2 -0
  32. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  33. package/dist/cli/main.js +3 -0
  34. package/dist/cli/main.js.map +1 -1
  35. package/dist/core/agentes.js +53 -2
  36. package/dist/core/agentes.js.map +1 -1
  37. package/dist/core/aprendido.js +68 -0
  38. package/dist/core/aprendido.js.map +1 -0
  39. package/dist/core/comandoExterno.js +29 -0
  40. package/dist/core/comandoExterno.js.map +1 -1
  41. package/dist/core/compararCapturas.js +62 -4
  42. package/dist/core/compararCapturas.js.map +1 -1
  43. package/dist/core/entrega.js +55 -10
  44. package/dist/core/entrega.js.map +1 -1
  45. package/dist/core/geometriaDePantalla.js +74 -5
  46. package/dist/core/geometriaDePantalla.js.map +1 -1
  47. package/dist/core/gestorDeTareas.js +2 -0
  48. package/dist/core/gestorDeTareas.js.map +1 -1
  49. package/dist/core/idiomaDeLaRespuesta.js +43 -0
  50. package/dist/core/idiomaDeLaRespuesta.js.map +1 -1
  51. package/dist/core/metodoDeComparacionVisual.js +71 -0
  52. package/dist/core/metodoDeComparacionVisual.js.map +1 -0
  53. package/dist/core/notify.js +2 -0
  54. package/dist/core/notify.js.map +1 -1
  55. package/dist/core/nucleoXone.js +42 -0
  56. package/dist/core/nucleoXone.js.map +1 -1
  57. package/dist/core/paqueteDeSoporte.js +2 -0
  58. package/dist/core/paqueteDeSoporte.js.map +1 -1
  59. package/dist/core/ports.js.map +1 -1
  60. package/dist/core/stitch.js +43 -0
  61. package/dist/core/stitch.js.map +1 -1
  62. package/dist/core/tareas.js.map +1 -1
  63. package/dist/core/textos.js +10 -3
  64. package/dist/core/textos.js.map +1 -1
  65. package/dist/web/servidor/arranque.js +40 -3
  66. package/dist/web/servidor/arranque.js.map +1 -1
  67. package/dist/web/servidor/consolaDeTarea.js +3 -6
  68. package/dist/web/servidor/consolaDeTarea.js.map +1 -1
  69. package/dist/web/servidor/corredorDeTareas.js +21 -4
  70. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  71. package/dist/web/servidor/transporte.js +1 -0
  72. package/dist/web/servidor/transporte.js.map +1 -1
  73. package/package.json +2 -2
  74. package/skills/xone-development/lib/aUtf8.mjs +68 -0
  75. package/skills/xone-development/scripts/xone-a-utf8 +60 -0
@@ -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";
@@ -73,7 +75,8 @@ import { encenderTrazaDeErrores } from "../../trazaDeErroresEnDisco.js";
73
75
  import { anotarPaso } from "../../../core/trazaDeErrores.js";
74
76
  import { entornoConDepuracion } from "../../turno/depuracion.js";
75
77
  import { detalleDe, parametrosDe } from "../../turno/resumenDeTool.js";
76
- 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";
77
80
  import { apartarMemoria, cargarMemoria, fotoSaneada, guardarMemoria, saldarColgadas, textoDeInterrupcion, textoDeMemoriaDescartada } from "./memoriaTrueforge.js";
78
81
  import { crearNota, sobrantes } from "./notas.js";
79
82
  import { crearMemoriaDeEspecialistas } from "./memoriaDeEspecialistas.js";
@@ -127,6 +130,22 @@ const BUCLE_DE_CALIDAD = [
127
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",
128
131
  "di lo que sigue faltando en vez de darlo por hecho.",
129
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
+ }
130
149
  /**
131
150
  * Lo que el orquestador tiene que saber para DELEGAR en ESTE motor. Su prompt es el de siempre
132
151
  * (`promptOrquestador`), que habla de `task` y de la ficha que va en su descripción: aquí la
@@ -176,6 +195,27 @@ export const TRABAJO_SIN_PERSONA = [
176
195
  " DECISIONES que tomaste tú sin preguntar (cada duda, lo que elegiste y por qué) y lo que no pudiste comprobar o",
177
196
  " queda pendiente.",
178
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
+ }
179
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é.";
180
220
  /** Un comando que saca una captura de pantalla del aparato. */
181
221
  export const COMANDO_DE_CAPTURA = /xone-captura-android|xone-hotswap\s+shot\b|\bscreencap\b/;
@@ -343,6 +383,11 @@ export function notaDeDelegacion(agentes, opciones = {}) {
343
383
  "entiende `create_sub_agent` y `name`. Las fichas de los especialistas:",
344
384
  ...agentes.map((a) => `- ${a.nombre}: ${fichaDeAgente(a, opciones)}`),
345
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
+ })(),
346
391
  ...(opciones.conEsperas === true
347
392
  ? [
348
393
  "",
@@ -459,6 +504,25 @@ function preguntaDeLaFoto(foto) {
459
504
  };
460
505
  }
461
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
+ }
462
526
  /**
463
527
  * `agregarNota` no vive todavía en `SesionReal` (IXCODE-4, Task 6 se la añade ahí como opcional,
464
528
  * compartida con deepagents): se ensancha el tipo de retorno AQUÍ, en vez de esperar a esa task,
@@ -576,6 +640,9 @@ export async function abrirSesionTrueforge(opciones) {
576
640
  let capturasDelTurno = [];
577
641
  /** Lo que dieron las comprobaciones visuales del turno, una línea cada una, para el juez de QA (`ResultadoDeTurno`). */
578
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;
579
646
  const anotarComprobacion = (linea) => {
580
647
  comprobacionesDelTurno = [...comprobacionesDelTurno, linea].slice(-TOPE_DE_COMPROBACIONES_AL_JUEZ);
581
648
  };
@@ -599,6 +666,30 @@ export async function abrirSesionTrueforge(opciones) {
599
666
  const detencion = crearControlDeDetencion(HILO_RAIZ);
600
667
  /** Tool calls tiradas por DETENER en la sesión: si se queda en cero, el filtro es solo red. */
601
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
+ };
602
693
  const capacidadDeNotasDeLaSesion = capacidadDeNotas(notas, {
603
694
  detencion,
604
695
  nombreDe: (h) => quienEs.get(h) ?? h,
@@ -805,6 +896,8 @@ export async function abrirSesionTrueforge(opciones) {
805
896
  ...herramientasDeJuicio(),
806
897
  ...(carpeta === undefined ? [] : [crearTraerDeLaMaquina({ carpeta, alEscribir: anotarArtefacto })]),
807
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" }),
808
901
  ...(opciones.puedeProponerTareas === true
809
902
  ? [crearProponerTareas((p) => void propuestasPorAnunciar.push(p))]
810
903
  : []),
@@ -857,6 +950,8 @@ export async function abrirSesionTrueforge(opciones) {
857
950
  ...(carpeta !== undefined && agente.ejecucion === true
858
951
  ? [crearDiferenciaDeCapturas({ leerArtefacto: async (nombre) => readFileSync(join(carpeta, nombre)) })]
859
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 })] : []),
860
955
  // Y marcar en el plan lo comprobado, con el reparto de deepagents (`recibeMarcarCriterios`).
861
956
  ...(recibeMarcarCriterios(agente) ? [crearMarcarCriteriosDelPlan({ raiz })] : []),
862
957
  // El BUCLE del desarrollador: quien puede llamar al de pruebas también tiene el crítico y la medida, para
@@ -922,13 +1017,18 @@ export async function abrirSesionTrueforge(opciones) {
922
1017
  * su nombre; el entorno de NUESTRA shell; y la lectura de ESTAS dos carpetas de la sesión, que
923
1018
  * es donde sus scripts dejan las capturas. Codex y OpenCode siguen sin shell.
924
1019
  */
925
- 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
926
1024
  ? {
927
- scripts: scriptsDeLasSkills(raiz, agente.skills),
1025
+ scripts: scriptsDeLasSkills(raiz, delHijo.skills),
928
1026
  entorno: entornoDeLaShellDelProyecto(raiz, carpeta),
929
1027
  lecturas: [carpeta, carpetaDeHotswap(carpeta)],
930
1028
  }
931
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;
932
1032
  /**
933
1033
  * Lo que sus comandos dejen en `/artefactos/` se ANUNCIA al volver, con la MISMA foto que nuestra
934
1034
  * shell (`fotoDeArtefactos`): por nuestra shell se anunciaba y por la suya no, y una captura
@@ -961,12 +1061,23 @@ export async function abrirSesionTrueforge(opciones) {
961
1061
  peticion: () => ({
962
1062
  motor,
963
1063
  cwd: raiz,
964
- 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"),
965
1072
  tarea: params.request.input,
966
1073
  ...(agente.modelo === undefined ? {} : { modelo: agente.modelo }),
967
1074
  permitirEscritura: !agente.soloLectura,
968
1075
  agente: agente.nombre,
1076
+ skills: repartirSkills(delHijo, disponibles).suyas,
969
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 } : {}),
970
1081
  }),
971
1082
  senal: () => aborto?.signal,
972
1083
  }),
@@ -1038,6 +1149,8 @@ export async function abrirSesionTrueforge(opciones) {
1038
1149
  notas: capacidadDeNotasDeLaSesion,
1039
1150
  puedeLlamar: (a) => conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0,
1040
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 } : {}),
1041
1154
  vueltas: (a) => a.ejecucion === true
1042
1155
  ? capacidadDeAvisoDeVueltas({ vueltasDe: (hilo) => capturasDeCadaHilo.get(hilo) ?? 0, umbrales: UMBRALES_DE_CAPTURAS, texto: textoDeCapturas })
1043
1156
  : conBucleDelDesarrollador && (a.llama?.length ?? 0) > 0
@@ -1058,7 +1171,10 @@ export async function abrirSesionTrueforge(opciones) {
1058
1171
  notaDeTools(nombres, agente === undefined ? LIMITE_DEL_GENERICO : LIMITES_DE[clase]),
1059
1172
  ...(nombres.includes(NOMBRE_ATRIBUTOS_XONE) ? [USO_DE_XONE_ATRIBUTOS] : []),
1060
1173
  ...(nombres.includes(NOMBRE_CONVERTIR_A_P) ? [USO_DE_CONVERTIR_A_P] : []),
1174
+ ...(nombres.includes(NOMBRE_APUNTAR_APRENDIZAJE) ? [USO_DE_APUNTAR_APRENDIZAJE] : []),
1061
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));
1062
1178
  const instrucciones = agente === undefined
1063
1179
  ? `${IDIOMA_DEL_ESPECIALISTA}\n\n${nota} Contesta con lo que encuentres y dónde.`
1064
1180
  : [
@@ -1068,6 +1184,7 @@ export async function abrirSesionTrueforge(opciones) {
1068
1184
  "",
1069
1185
  nota,
1070
1186
  anuncioDeSkills(agente, catalogo),
1187
+ ...(aprendido === undefined ? [] : ["", aprendido]),
1071
1188
  ...(conBucleDelDesarrollador && (agente.llama?.length ?? 0) > 0 ? ["", textoDelBucle(agente.llama ?? [], { conCritica: carpeta !== undefined, conFuentes: opciones.fuentes !== undefined })] : []),
1072
1189
  ]
1073
1190
  .filter((l) => l !== undefined)
@@ -1163,7 +1280,7 @@ export async function abrirSesionTrueforge(opciones) {
1163
1280
  hijosConMemoria.clear();
1164
1281
  const definicion = {
1165
1282
  modelClient: llm,
1166
- 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 : ""].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"),
1167
1284
  // Por TURNO, porque el raíz se rehace desde su foto al final de cada uno (ver `turno`).
1168
1285
  iterationLimit: LIMITE_DE_LLAMADAS_DEL_RAIZ,
1169
1286
  };
@@ -1182,6 +1299,7 @@ export async function abrirSesionTrueforge(opciones) {
1182
1299
  capacidadDeFecha(),
1183
1300
  capacidadDeNotasDeLaSesion,
1184
1301
  capacidadDeInformes,
1302
+ capacidadDeIdioma,
1185
1303
  ]),
1186
1304
  /**
1187
1305
  * **La conversación se RESUME a un umbral que depende de la ventana del modelo**
@@ -1387,6 +1505,12 @@ export async function abrirSesionTrueforge(opciones) {
1387
1505
  // Las comprobaciones VISUALES, para el juez: lo que dijeron, medido aquí y no contado por el agente.
1388
1506
  if (llamada !== undefined && (llamada.nombre === NOMBRE_COMPARAR_CAPTURAS || llamada.nombre === "xone_critica_visual") && typeof evento.content === "string") {
1389
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
+ }
1390
1514
  }
1391
1515
  const chars = typeof evento.content === "string" ? evento.content.length : 0;
1392
1516
  diagnostico?.resultado?.(llamada?.nombre, llamada === undefined ? undefined : detalleDe(llamada.nombre, llamada.args), chars);
@@ -1509,6 +1633,8 @@ export async function abrirSesionTrueforge(opciones) {
1509
1633
  throw new Error("la sesión está cerrada");
1510
1634
  const t0 = Date.now();
1511
1635
  cancelado = false;
1636
+ recordatoriosEnElTurno = 0;
1637
+ recordatorioDeIdioma = undefined;
1512
1638
  detencion.reiniciar();
1513
1639
  const instantanea = await tomarInstantanea(raiz, opciones.entorno.git);
1514
1640
  const tope = opciones.topeDeRondas ?? MAX_APPROVAL_ROUNDS;
@@ -1519,6 +1645,8 @@ export async function abrirSesionTrueforge(opciones) {
1519
1645
  externosDelTurno = 0;
1520
1646
  capturasDelTurno = [];
1521
1647
  comprobacionesDelTurno = [];
1648
+ bloqueantesVisualesDelTurno = [];
1649
+ horaDeLaUltimaCritica = undefined;
1522
1650
  /** El encargo de ESTE turno tal cual se pidió, y el mismo con la última pregunta y su
1523
1651
  * respuesta al lado, que es lo que se juzga y se repara (ver `flujo`). Ausente = no consta. */
1524
1652
  let encargoDelTurno;
@@ -1576,7 +1704,7 @@ export async function abrirSesionTrueforge(opciones) {
1576
1704
  type: "user.tool_response",
1577
1705
  thread_id: q.hilo,
1578
1706
  tool_call_id: q.id,
1579
- content: elegida ?? RESPUESTA_AUTONOMA_SIN_RECOMENDADA,
1707
+ content: elegida === undefined ? RESPUESTA_AUTONOMA_SIN_RECOMENDADA : respuestaAutonomaRecomendada(elegida),
1580
1708
  });
1581
1709
  }
1582
1710
  lote = respuestas;
@@ -1752,8 +1880,17 @@ export async function abrirSesionTrueforge(opciones) {
1752
1880
  mensajeCerrado = false;
1753
1881
  ultimoMensaje += evento.texto;
1754
1882
  }
1755
- 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
+ }
1756
1892
  mensajeCerrado = true;
1893
+ }
1757
1894
  yield evento;
1758
1895
  }
1759
1896
  /**
@@ -1844,6 +1981,9 @@ export async function abrirSesionTrueforge(opciones) {
1844
1981
  */
1845
1982
  let observacionesVisuales = [];
1846
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 &&
1847
1987
  tocaCriticarPantalla({ hayCritico: opciones.criticaVisual !== undefined, capturas: capturasDelTurno.length, yaDisparo: visualYaDisparo, intento })) {
1848
1988
  visualYaDisparo = true;
1849
1989
  const captura = capturasDelTurno[capturasDelTurno.length - 1];
@@ -2036,6 +2176,17 @@ export async function abrirSesionTrueforge(opciones) {
2036
2176
  // La pregunta que deja ESTE turno: la de un turno anterior se consume al empezar (`enEspera`).
2037
2177
  ...(preguntaEnEspera === undefined ? {} : { preguntaPendiente: consultaDe(preguntaEnEspera.args).pregunta }),
2038
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
+ })(),
2039
2190
  ...(hallazgosDelTurno.length === 0 ? {} : { hallazgos: hallazgosDelTurno }),
2040
2191
  ...(preexistentesDelTurno === undefined ? {} : { preexistentes: preexistentesDelTurno }),
2041
2192
  ...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),