xonecode 0.8.2 → 0.8.10
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.
- package/dist/agent/config/modelos.js +20 -1
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/grafo/copiarArtefacto.js +128 -0
- package/dist/agent/grafo/copiarArtefacto.js.map +1 -0
- package/dist/agent/grafo/perfiles.js +69 -1
- package/dist/agent/grafo/perfiles.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +40 -2
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +64 -2
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/turno/juezDelTurnoConModelo.js +37 -0
- package/dist/agent/turno/juezDelTurnoConModelo.js.map +1 -0
- package/dist/agent/turno/registroDeFallos.js +51 -0
- package/dist/agent/turno/registroDeFallos.js.map +1 -0
- package/dist/agent/turno/turnoReal.js +335 -155
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/main.js +13 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/core/agentes.js +9 -0
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/esfuerzo.js +31 -12
- package/dist/core/esfuerzo.js.map +1 -1
- package/dist/core/fallos.js +108 -0
- package/dist/core/fallos.js.map +1 -0
- package/dist/core/juezDelTurno.js +140 -0
- package/dist/core/juezDelTurno.js.map +1 -0
- package/dist/vendor/hitl.js +83 -12
- package/dist/vendor/hitl.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,8 +38,34 @@ export function tocaCriticarPantalla(estado) {
|
|
|
38
38
|
* —eso lo sabe él o no lo sabe—, solo lo que se ha medido y lo único que importa aquí: que no
|
|
39
39
|
* invente nada para que el error desaparezca, que es justo lo que XOne no le reprocha.
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Lo que se le pide al agente cuando el verificador o el crítico ven algo.
|
|
43
|
+
*
|
|
44
|
+
* **Lleva el OBJETIVO delante, y ése es el arreglo.** Esta petición entra como
|
|
45
|
+
* `HumanMessage` en el mismo hilo (ver abajo), así que a partir de ese momento pasa a ser
|
|
46
|
+
* «el último humano» — y `conservarElEncargo` protege justo ése. O sea que sin el objetivo
|
|
47
|
+
* dentro, el encargo del usuario deja de estar protegido y lo sustituye la lista de
|
|
48
|
+
* hallazgos: el agente no se despista, le hemos cambiado el encargo por debajo y encima se
|
|
49
|
+
* lo recordamos en cada llamada.
|
|
50
|
+
*
|
|
51
|
+
* MEDIDO en un turno real: se pidió «tengo un error al ejecutar la app, arréglalo», se
|
|
52
|
+
* arregló (faltaba un `;` en `funciones.js`), y entonces el crítico visual vio tres textos
|
|
53
|
+
* recortados en otra pantalla —rotos desde antes, y sin relación con el arreglo—. El turno
|
|
54
|
+
* se fue a rediseñar el menú.
|
|
55
|
+
*
|
|
56
|
+
* **Y el objetivo es lo que decide si un hallazgo es trabajo o es ruido.** El mismo
|
|
57
|
+
* «BASICOS PLUS se corta» es ruido si el objetivo era arrancar la app, y es EL trabajo si
|
|
58
|
+
* el objetivo era arreglar los fallos visuales. Eso no lo dice el fichero que se tocó —el
|
|
59
|
+
* heurístico que se descartó— ni lo puede decidir el harness: lo decide el objetivo, y por
|
|
60
|
+
* eso viaja con la petición en vez de quedarse fuera.
|
|
61
|
+
*/
|
|
62
|
+
export function textoDeReparacion(hallazgos, observaciones,
|
|
63
|
+
/** El encargo ORIGINAL de este turno. Ausente = no se pudo saber, y entonces no se afirma. */
|
|
64
|
+
objetivo) {
|
|
42
65
|
const lineas = [];
|
|
66
|
+
if (objetivo !== undefined && objetivo.trim() !== "") {
|
|
67
|
+
lineas.push("TU OBJETIVO EN ESTE TURNO SIGUE SIENDO ÉSTE, y no ha cambiado:", "", objetivo.trim(), "", "Lo que viene debajo es lo que han visto las comprobaciones AL PASAR. No es un encargo", "nuevo.", "");
|
|
68
|
+
}
|
|
43
69
|
if (hallazgos.length > 0) {
|
|
44
70
|
const errores = hallazgos.filter((h) => h.severidad === "error").length;
|
|
45
71
|
lineas.push(`El simulador de XOne ha revisado lo que acabas de escribir y ha encontrado ${errores} error(es):`, ...hallazgos.map((h) => `- ${h.severidad === "error" ? "ERROR" : "aviso"} ${h.code}${h.fichero === undefined ? "" : ` en ${h.fichero}${h.linea === undefined ? "" : `:${h.linea}`}`}: ${h.mensaje}`));
|
|
@@ -55,7 +81,16 @@ export function textoDeReparacion(hallazgos, observaciones) {
|
|
|
55
81
|
*/
|
|
56
82
|
lineas.push("Y mirando una captura de la pantalla en el aparato se ve esto (la REDACCIÓN no es de", "fiar: describe mal la causa. Fíate de QUÉ control señala, no de su explicación, y", "míralo tú en el código):", ...observaciones.map((o) => `- ${o}`));
|
|
57
83
|
}
|
|
58
|
-
lineas.push("",
|
|
84
|
+
lineas.push("",
|
|
85
|
+
// El reparto: lo que sirve al objetivo se arregla, y lo demás se CUENTA. Sin esta
|
|
86
|
+
// frase, una lista de hallazgos se lee como la tarea entera — que es lo que pasó.
|
|
87
|
+
...(objetivo === undefined || objetivo.trim() === ""
|
|
88
|
+
? ["Corrige lo que puedas."]
|
|
89
|
+
: [
|
|
90
|
+
"Arregla lo que haga falta PARA CUMPLIR TU OBJETIVO. Lo que no tenga que ver con él",
|
|
91
|
+
"—algo que ya estaba roto antes y que tu cambio no ha causado— NO lo toques: dilo en",
|
|
92
|
+
"tu respuesta para que lo decida quien te lo encargó. Nadie te ha pedido eso.",
|
|
93
|
+
]), "No inventes atributos, funciones ni propiedades para que", "desaparezcan: XOne ignora lo desconocido en silencio y el simulador lo detecta.", "Si algo no sabes cómo corregirlo, dilo en vez de intentar otra cosa.");
|
|
59
94
|
return lineas.join("\n");
|
|
60
95
|
}
|
|
61
96
|
import { aPendiente, ficheroDe, cambioDe, buildResume } from "./interrupts.js";
|
|
@@ -76,6 +111,8 @@ import { crearDiagnosticoDeTools } from "./diagnosticoDeTools.js";
|
|
|
76
111
|
import { indiceEnDisco } from "../navegacion/indiceEnDisco.js";
|
|
77
112
|
import { hechosDelProyectoDe } from "../navegacion/hechosEnDisco.js";
|
|
78
113
|
import { conHechosDelProyecto } from "../../core/hechosDelProyecto.js";
|
|
114
|
+
import { accionDelJuez } from "../../core/juezDelTurno.js";
|
|
115
|
+
import { crearRegistroDeFallos } from "./registroDeFallos.js";
|
|
79
116
|
/**
|
|
80
117
|
* Recorre la raíz y devuelve las rutas en el ESPACIO VIRTUAL del backend.
|
|
81
118
|
*
|
|
@@ -417,6 +454,14 @@ export async function abrirSesionReal(opciones) {
|
|
|
417
454
|
* La foto es POR TURNO, no por sesión: con una sola, el diff del turno 3 incluiría lo que
|
|
418
455
|
* escribió el turno 1.
|
|
419
456
|
*/
|
|
457
|
+
/**
|
|
458
|
+
* Los NOMBRES de lo que el turno ha ido haciendo, para el registro de fallos.
|
|
459
|
+
*
|
|
460
|
+
* Solo nombres, nunca argumentos — la misma regla que `core/events.ts`. Un `400` a secas
|
|
461
|
+
* no dice nada; «tras delegar en dos subagentes» sí, y eso es lo que hubo que reconstruir
|
|
462
|
+
* a mano de las trazas pegadas.
|
|
463
|
+
*/
|
|
464
|
+
const pasosDelTurno = [];
|
|
420
465
|
const turno = async (peticion, piel) => {
|
|
421
466
|
// Un turno sobre una sesión ya cerrada reviviría un hilo que su dueño soltó al cambiar
|
|
422
467
|
// de proyecto. Falla en vez de trabajar en silencio sobre la raíz equivocada.
|
|
@@ -533,181 +578,278 @@ export async function abrirSesionReal(opciones) {
|
|
|
533
578
|
if (intento > 0)
|
|
534
579
|
yield { tipo: "reparacion", intento, tope: TOPE_REPARACIONES };
|
|
535
580
|
reparar = false;
|
|
536
|
-
yield
|
|
581
|
+
// Se acumula el texto AL PASAR en vez de `yield*`: es lo que el juez del turno tiene
|
|
582
|
+
// que cruzar con los hechos, y pedírselo al estado después sería leer dos veces lo
|
|
583
|
+
// mismo. No cuesta nada — solo se mira el `tipo`.
|
|
584
|
+
let respuestaDeLaPasada = "";
|
|
585
|
+
for await (const evento of eventos) {
|
|
586
|
+
if (evento.tipo === "token")
|
|
587
|
+
respuestaDeLaPasada += evento.texto;
|
|
588
|
+
if (evento.tipo === "tool")
|
|
589
|
+
pasosDelTurno.push(evento.nombre);
|
|
590
|
+
yield evento;
|
|
591
|
+
}
|
|
537
592
|
// Lo primero al agotarse la pasada: lo que el agente dejó escrito sin preguntar. Va
|
|
538
593
|
// ANTES de la decisión de cierre y antes del veredicto porque es trabajo TERMINADO de
|
|
539
594
|
// esta pasada — anunciarlo después del `fin` lo pintaría fuera del turno, que es el
|
|
540
595
|
// mismo motivo por el que el verificador está cosido aquí dentro.
|
|
541
596
|
for (const artefacto of artefactosDeLaPasada.splice(0))
|
|
542
597
|
yield { tipo: "artefacto", artefacto };
|
|
543
|
-
if ((await leerPendientes()).lista.length > 0) {
|
|
544
|
-
// Quedan escrituras por aprobar. Si el bucle va a seguir —hay quien apruebe y no se
|
|
545
|
-
// ha agotado el tope de rondas— esta pasada NO es el final del turno y no cierra.
|
|
546
|
-
// Son las mismas dos condiciones del `break` del bucle, y tienen que serlo: si
|
|
547
|
-
// divergen, un turno se queda sin `fin` o cierra dos veces.
|
|
548
|
-
cerrarRonda = !(ronda < topeDeRondas && opciones.pedirAprobacion !== undefined);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
cerrarRonda = true;
|
|
552
|
-
const cambios = (await instantanea.cambios()).filter((c) => c.clase !== "borrado" && !c.ruta.startsWith(".xonecode/") && c.ruta !== ".xonecode");
|
|
553
|
-
rondaEscribio = cambios.length > 0;
|
|
554
|
-
if (!rondaEscribio) {
|
|
555
|
-
// No hay nada que verificar, y eso NO es un verde. Se DICE, porque este motivo
|
|
556
|
-
// acaba en la tarjeta de una tarea aparcada y «no se sabe» tiene que distinguirse
|
|
557
|
-
// de «el simulador no está» y de «el turno se cortó antes de llegar».
|
|
558
|
-
motivoSinVerificar = "el turno no escribió ningún fichero del proyecto";
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
if (opciones.verifier === undefined) {
|
|
562
|
-
motivoSinVerificar = "esta ejecución no tiene verificador";
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
yield { tipo: "fase", fase: "verificando" };
|
|
566
|
-
let informe;
|
|
567
|
-
try {
|
|
568
|
-
informe = await opciones.verifier.verificar(raiz);
|
|
569
|
-
}
|
|
570
|
-
catch (e) {
|
|
571
|
-
// Que no esté el binario NO es un fallo del proyecto, y se dice como tal. El aviso
|
|
572
|
-
// determinista del final saldrá igualmente: «no ha corrido» sigue siendo verdad.
|
|
573
|
-
motivoSinVerificar = e instanceof Error ? e.message : String(e);
|
|
574
|
-
yield { tipo: "aviso", texto: `⚠ no se pudo verificar: ${motivoSinVerificar}`, severidad: "aviso" };
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
// Los hallazgos se reparten entre los ficheros que ESTE turno tocó y los demás. El
|
|
578
|
-
// simulador mira el proyecto entero —es su API—, y un error que ya estaba en un
|
|
579
|
-
// fichero que el agente no abrió no es del agente. Un hallazgo sin fichero no se
|
|
580
|
-
// puede atribuir: se enseña con los del turno, que es el lado conservador.
|
|
581
|
-
const tocados = new Set(cambios.map((c) => resolverRuta(raiz, c.ruta)));
|
|
582
|
-
const delTurno = informe.hallazgos.filter((h) => h.fichero === undefined || tocados.has(resolverRuta(h.fichero)));
|
|
583
|
-
const preexistentes = informe.hallazgos.length - delTurno.length;
|
|
584
|
-
const errores = delTurno.filter((h) => h.severidad === "error").length;
|
|
585
|
-
const hallazgos = delTurno.map((h) => ({
|
|
586
|
-
code: h.code,
|
|
587
|
-
severidad: h.severidad,
|
|
588
|
-
mensaje: h.mensaje,
|
|
589
|
-
...(h.fichero === undefined ? {} : { fichero: relative(raiz, h.fichero) }),
|
|
590
|
-
...(h.linea === undefined ? {} : { linea: h.linea }),
|
|
591
|
-
}));
|
|
592
|
-
// Lo mismo que va al evento, apuntado para el retorno: NO se recalcula ni se
|
|
593
|
-
// re-parsea de la bitácora, que es cómo dos copias de una cuenta acaban discrepando.
|
|
594
|
-
veredicto = errores === 0 ? "verde" : "rojo";
|
|
595
|
-
hallazgosDelTurno = hallazgos;
|
|
596
|
-
// El reparto entero, no solo su mitad. Aquí sí se apunta el cero: el evento lo omite
|
|
597
|
-
// porque una línea de consola que dice «y 0 más» no dice nada, pero quien recibe
|
|
598
|
-
// `hallazgos` necesita saber que la lista está filtrada — y con cero también.
|
|
599
|
-
preexistentesDelTurno = preexistentes;
|
|
600
|
-
yield {
|
|
601
|
-
tipo: "verificacion",
|
|
602
|
-
verde: errores === 0,
|
|
603
|
-
errores,
|
|
604
|
-
avisos: delTurno.length - errores,
|
|
605
|
-
hallazgos,
|
|
606
|
-
...(preexistentes > 0 ? { preexistentes } : {}),
|
|
607
|
-
};
|
|
608
598
|
/**
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
* Ésta es la diferencia entre que la crítica ocurra y que ocurra cuando el modelo se
|
|
612
|
-
* acuerde. Es el mismo punto y el mismo mecanismo que el resto del lazo —y el mismo que
|
|
613
|
-
* el `RubricMiddleware` de deepagents: enganchar donde el agente iba a terminar y
|
|
614
|
-
* devolver el feedback como mensaje de USUARIO en el mismo hilo—.
|
|
599
|
+
* El verificador y el crítico, en su propio generador.
|
|
615
600
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
601
|
+
* Se extrae por una razón de FLUJO y no de estética: este cuerpo tiene CUATRO
|
|
602
|
+
* `return` que significan «no hay nada que reparar» —escrituras pendientes, una
|
|
603
|
+
* ronda que no escribió, sin verificador, un verificador que revienta—, y todos
|
|
604
|
+
* ellos son el final feliz del turno. Con el juez escrito detrás de ellos no se
|
|
605
|
+
* llamaba nunca en esos casos, que son justo los que más falta hace mirar: un turno
|
|
606
|
+
* que no escribió nada es el candidato número uno a no haber cumplido el encargo.
|
|
620
607
|
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
* **Límite declarado**: esto cuelga del mismo bloque que el verificador, así que solo
|
|
625
|
-
* corre en un turno que ESCRIBIÓ ficheros del proyecto. Un turno de pura inspección
|
|
626
|
-
* —«ve a esa pantalla y dime qué ves»— no pasa por aquí, y no debe: la pregunta que
|
|
627
|
-
* contesta este enganche es «¿quedó bien lo que acabas de escribir?». Para mirar sin
|
|
628
|
-
* escribir está la tool `xone_critica_visual`, que el orquestador invoca cuando quiere.
|
|
608
|
+
* Metido aquí, esos `return` terminan solo ESTE generador y el juez de abajo corre
|
|
609
|
+
* igual. Comprobado con el test del cableado, que sin esto no llamaba al juez.
|
|
629
610
|
*/
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
611
|
+
async function* verificarYCriticar() {
|
|
612
|
+
if ((await leerPendientes()).lista.length > 0) {
|
|
613
|
+
// Quedan escrituras por aprobar. Si el bucle va a seguir —hay quien apruebe y no se
|
|
614
|
+
// ha agotado el tope de rondas— esta pasada NO es el final del turno y no cierra.
|
|
615
|
+
// Son las mismas dos condiciones del `break` del bucle, y tienen que serlo: si
|
|
616
|
+
// divergen, un turno se queda sin `fin` o cierra dos veces.
|
|
617
|
+
cerrarRonda = !(ronda < topeDeRondas && opciones.pedirAprobacion !== undefined);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
cerrarRonda = true;
|
|
621
|
+
const cambios = (await instantanea.cambios()).filter((c) => c.clase !== "borrado" && !c.ruta.startsWith(".xonecode/") && c.ruta !== ".xonecode");
|
|
622
|
+
rondaEscribio = cambios.length > 0;
|
|
623
|
+
if (!rondaEscribio) {
|
|
624
|
+
// No hay nada que verificar, y eso NO es un verde. Se DICE, porque este motivo
|
|
625
|
+
// acaba en la tarjeta de una tarea aparcada y «no se sabe» tiene que distinguirse
|
|
626
|
+
// de «el simulador no está» y de «el turno se cortó antes de llegar».
|
|
627
|
+
motivoSinVerificar = "el turno no escribió ningún fichero del proyecto";
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (opciones.verifier === undefined) {
|
|
631
|
+
motivoSinVerificar = "esta ejecución no tiene verificador";
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
yield { tipo: "fase", fase: "verificando" };
|
|
635
|
+
let informe;
|
|
640
636
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
637
|
+
informe = await opciones.verifier.verificar(raiz);
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
// Que no esté el binario NO es un fallo del proyecto, y se dice como tal. El aviso
|
|
641
|
+
// determinista del final saldrá igualmente: «no ha corrido» sigue siendo verdad.
|
|
642
|
+
motivoSinVerificar = e instanceof Error ? e.message : String(e);
|
|
643
|
+
yield { tipo: "aviso", texto: `⚠ no se pudo verificar: ${motivoSinVerificar}`, severidad: "aviso" };
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
// Los hallazgos se reparten entre los ficheros que ESTE turno tocó y los demás. El
|
|
647
|
+
// simulador mira el proyecto entero —es su API—, y un error que ya estaba en un
|
|
648
|
+
// fichero que el agente no abrió no es del agente. Un hallazgo sin fichero no se
|
|
649
|
+
// puede atribuir: se enseña con los del turno, que es el lado conservador.
|
|
650
|
+
const tocados = new Set(cambios.map((c) => resolverRuta(raiz, c.ruta)));
|
|
651
|
+
const delTurno = informe.hallazgos.filter((h) => h.fichero === undefined || tocados.has(resolverRuta(h.fichero)));
|
|
652
|
+
const preexistentes = informe.hallazgos.length - delTurno.length;
|
|
653
|
+
const errores = delTurno.filter((h) => h.severidad === "error").length;
|
|
654
|
+
const hallazgos = delTurno.map((h) => ({
|
|
655
|
+
code: h.code,
|
|
656
|
+
severidad: h.severidad,
|
|
657
|
+
mensaje: h.mensaje,
|
|
658
|
+
...(h.fichero === undefined ? {} : { fichero: relative(raiz, h.fichero) }),
|
|
659
|
+
...(h.linea === undefined ? {} : { linea: h.linea }),
|
|
660
|
+
}));
|
|
661
|
+
// Lo mismo que va al evento, apuntado para el retorno: NO se recalcula ni se
|
|
662
|
+
// re-parsea de la bitácora, que es cómo dos copias de una cuenta acaban discrepando.
|
|
663
|
+
veredicto = errores === 0 ? "verde" : "rojo";
|
|
664
|
+
hallazgosDelTurno = hallazgos;
|
|
665
|
+
// El reparto entero, no solo su mitad. Aquí sí se apunta el cero: el evento lo omite
|
|
666
|
+
// porque una línea de consola que dice «y 0 más» no dice nada, pero quien recibe
|
|
667
|
+
// `hallazgos` necesita saber que la lista está filtrada — y con cero también.
|
|
668
|
+
preexistentesDelTurno = preexistentes;
|
|
669
|
+
yield {
|
|
670
|
+
tipo: "verificacion",
|
|
671
|
+
verde: errores === 0,
|
|
672
|
+
errores,
|
|
673
|
+
avisos: delTurno.length - errores,
|
|
674
|
+
hallazgos,
|
|
675
|
+
...(preexistentes > 0 ? { preexistentes } : {}),
|
|
676
|
+
};
|
|
677
|
+
/**
|
|
678
|
+
* **El crítico visual, enganchado AQUÍ y no en el prompt del orquestador.**
|
|
679
|
+
*
|
|
680
|
+
* Ésta es la diferencia entre que la crítica ocurra y que ocurra cuando el modelo se
|
|
681
|
+
* acuerde. Es el mismo punto y el mismo mecanismo que el resto del lazo —y el mismo que
|
|
682
|
+
* el `RubricMiddleware` de deepagents: enganchar donde el agente iba a terminar y
|
|
683
|
+
* devolver el feedback como mensaje de USUARIO en el mismo hilo—.
|
|
684
|
+
*
|
|
685
|
+
* **No conduce nada**: mira las capturas que este turno ya dejó. Navegar hasta una
|
|
686
|
+
* pantalla necesita un agente (medido: el control que lleva a la Calculadora vive en un
|
|
687
|
+
* cajón cerrado y hay que abrirlo mirando el árbol), pero MIRAR lo ya fotografiado no.
|
|
688
|
+
* Sin capturas no opina, que es honesto: no hay nada que ver.
|
|
689
|
+
*
|
|
690
|
+
* Y que no se pueda preguntar NO es un rojo: se dice como aviso y el turno sigue, igual
|
|
691
|
+
* que con el binario del simulador.
|
|
692
|
+
*
|
|
693
|
+
* **Límite declarado**: esto cuelga del mismo bloque que el verificador, así que solo
|
|
694
|
+
* corre en un turno que ESCRIBIÓ ficheros del proyecto. Un turno de pura inspección
|
|
695
|
+
* —«ve a esa pantalla y dime qué ves»— no pasa por aquí, y no debe: la pregunta que
|
|
696
|
+
* contesta este enganche es «¿quedó bien lo que acabas de escribir?». Para mirar sin
|
|
697
|
+
* escribir está la tool `xone_critica_visual`, que el orquestador invoca cuando quiere.
|
|
698
|
+
*/
|
|
699
|
+
if (tocaCriticarPantalla({
|
|
700
|
+
hayCritico: opciones.criticaVisual !== undefined,
|
|
701
|
+
capturas: capturasDelTurno.length,
|
|
702
|
+
yaDisparo: visualYaDisparo,
|
|
703
|
+
intento,
|
|
704
|
+
})) {
|
|
705
|
+
visualYaDisparo = true;
|
|
706
|
+
// La ÚLTIMA: es el estado más reciente de la pantalla, y las anteriores pueden ser de
|
|
707
|
+
// antes del cambio que este turno acaba de hacer.
|
|
708
|
+
const captura = capturasDelTurno[capturasDelTurno.length - 1];
|
|
709
|
+
try {
|
|
710
|
+
const bytes = readFileSync(join(carpetaDeArtefactos, captura.nombre));
|
|
711
|
+
const visual = await opciones.criticaVisual({ base64: bytes.toString("base64"), mime: captura.mime ?? "image/png" }, captura.nombre);
|
|
712
|
+
if (visual.veredicto === "rojo" && visual.observaciones.length > 0) {
|
|
713
|
+
observacionesVisuales = visual.observaciones;
|
|
714
|
+
yield {
|
|
715
|
+
tipo: "aviso",
|
|
716
|
+
texto: `⚠ la captura de esta sesión enseña ${visual.observaciones.length} defecto(s) de pantalla`,
|
|
717
|
+
severidad: "aviso",
|
|
718
|
+
};
|
|
719
|
+
/**
|
|
720
|
+
* **Con el simulador en VERDE, el crítico REPORTA y no dispara reparación.**
|
|
721
|
+
*
|
|
722
|
+
* Antes sí la disparaba, y ésa era la tercera puerta de la misma deriva. MEDIDO
|
|
723
|
+
* dos veces en turnos reales: se pidió «arregla el error al ejecutar la app», se
|
|
724
|
+
* arregló, el conductor MIDIÓ que arranca y llega a Menu —o sea, objetivo
|
|
725
|
+
* cumplido y comprobado— y aun así el crítico veía cuatro textos recortados en
|
|
726
|
+
* otra pantalla, rotos desde antes, y el harness abría otra ronda «de
|
|
727
|
+
* reparación». El turno se iba a rediseñar el menú.
|
|
728
|
+
*
|
|
729
|
+
* Verde del simulador + objetivo cumplido no es un turno a medias: es un turno
|
|
730
|
+
* terminado con algo que decir. Las observaciones ya salen como `aviso` y van en
|
|
731
|
+
* la respuesta; quien decide si eso se toca es la persona, que es la misma regla
|
|
732
|
+
* de `preexistentes` — no se repara lo que no rompiste.
|
|
733
|
+
*
|
|
734
|
+
* En ROJO no cambia nada: las observaciones se suman a la petición que iba a
|
|
735
|
+
* salir igualmente, con el objetivo delante (`textoDeReparacion`), y no se gasta
|
|
736
|
+
* una vuelta extra.
|
|
737
|
+
*
|
|
738
|
+
* **El precio, declarado**: un turno que SÍ rompió el layout y dejó el simulador
|
|
739
|
+
* en verde ya no se autocorrige. Lo que queda es el aviso y lo que el propio
|
|
740
|
+
* agente haga dentro de su ronda —tiene la tool—, y eso se ve. Se prefiere eso a
|
|
741
|
+
* una ronda que arregla lo que nadie pidió, que es lo que pasaba.
|
|
742
|
+
*
|
|
743
|
+
* **Y un hueco de TEST, dicho y no escondido**: esta rama no tiene prueba de
|
|
744
|
+
* turno. El arnés de `turnoReal.test.ts` no sabe producir una captura —el
|
|
745
|
+
* crítico solo entra con un artefacto de imagen en `capturasDelTurno` y además
|
|
746
|
+
* LEE el fichero del disco—, así que atarlo pide cablear eso primero. Lo que sí
|
|
747
|
+
* está probado es `tocaCriticarPantalla` (cuándo entra el crítico) y el objetivo
|
|
748
|
+
* dentro de `textoDeReparacion` (qué recibe el agente cuando sí hay ronda).
|
|
749
|
+
*/
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
645
753
|
yield {
|
|
646
754
|
tipo: "aviso",
|
|
647
|
-
texto: `⚠
|
|
755
|
+
texto: `⚠ no se pudo criticar la pantalla: ${error instanceof Error ? error.name : "error"}`,
|
|
648
756
|
severidad: "aviso",
|
|
649
757
|
};
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (errores === 0)
|
|
761
|
+
return;
|
|
762
|
+
// Rojo. Tres salidas, y solo una de ellas es «inténtalo otra vez».
|
|
763
|
+
//
|
|
764
|
+
// La huella son los ERRORES, no todos los hallazgos: un aviso que va y viene no dice
|
|
765
|
+
// nada de si el error se está arreglando. Y se compara con la del veredicto anterior
|
|
766
|
+
// y no con «¿bajó el número?»: dos errores distintos en vez de dos iguales también es
|
|
767
|
+
// avance, y un modelo que arregla uno y rompe otro no debe quedarse bloqueado como si
|
|
768
|
+
// no hubiera hecho nada.
|
|
769
|
+
const huella = hallazgos
|
|
770
|
+
.filter((h) => h.severidad === "error")
|
|
771
|
+
.map((h) => `${h.code}|${h.fichero ?? ""}|${h.linea ?? ""}`)
|
|
772
|
+
.sort()
|
|
773
|
+
.join("\n");
|
|
774
|
+
if (huella === huellaPrevia) {
|
|
775
|
+
// Corregir no cambió nada: el mismo error, en el mismo sitio. Seguir sería gastar
|
|
776
|
+
// el tope —y aprobaciones humanas— en repetir el mismo cambio.
|
|
777
|
+
yield {
|
|
778
|
+
tipo: "bloqueado",
|
|
779
|
+
motivo: "no-progreso",
|
|
780
|
+
explicacion: `el intento ${intento} dejó los mismos ${errores} error(es) en los mismos sitios`,
|
|
781
|
+
};
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (intento >= TOPE_REPARACIONES) {
|
|
785
|
+
yield {
|
|
786
|
+
tipo: "bloqueado",
|
|
787
|
+
motivo: "tope-reparaciones",
|
|
788
|
+
explicacion: `tras ${intento} intento(s) siguen ${errores} error(es); se deja como está para que lo mires`,
|
|
789
|
+
};
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
// Se intenta otra vez. Esta pasada NO cierra el turno: el `fin` y los avisos van al
|
|
793
|
+
// final del intento que viene, o del que corte.
|
|
794
|
+
huellaPrevia = huella;
|
|
795
|
+
ultimosHallazgos = hallazgos;
|
|
796
|
+
intento += 1;
|
|
797
|
+
reparar = true;
|
|
798
|
+
cerrarRonda = false;
|
|
799
|
+
}
|
|
800
|
+
yield* verificarYCriticar();
|
|
801
|
+
/**
|
|
802
|
+
* **El turno se acaba aquí. ¿Pero hizo lo que se le pidió?**
|
|
803
|
+
*
|
|
804
|
+
* Ningún `if` contesta eso, y es la pregunta de quien lo encargó. MEDIDO en turnos
|
|
805
|
+
* reales, tres veces en la misma sesión: «el especialista se ha vuelto a cortar antes
|
|
806
|
+
* de darme el informe», «las dos tareas que lancé no llegaron a ejecutarse», «la
|
|
807
|
+
* devolución del verificador ha llegado cortada». En los tres el turno cerró y el
|
|
808
|
+
* resumen sonaba a trabajo terminado.
|
|
809
|
+
*
|
|
810
|
+
* Va DESPUÉS del verificador y FUERA de él a propósito: sus cuatro `return` son
|
|
811
|
+
* finales felices del turno —incluido «no escribió nada», que es el candidato número
|
|
812
|
+
* uno a no haber cumplido—, y el juez tiene que llegar a todos.
|
|
813
|
+
*
|
|
814
|
+
* **Y no se juzga un turno que va a seguir**: con `reparar` puesto viene otra pasada,
|
|
815
|
+
* así que preguntar aquí sería juzgar un trabajo a medias y avisar de algo que está a
|
|
816
|
+
* punto de arreglarse solo.
|
|
817
|
+
*
|
|
818
|
+
* El reparto es el de las tareas (`core/entrega.ts`): los HECHOS los mide el código y
|
|
819
|
+
* se le dan hechos, y lo único que se le pregunta es el juicio. Lo que no se entiende
|
|
820
|
+
* es `dudoso`, nunca `cumplido`: decir que se cumplió sin serlo cierra el turno en
|
|
821
|
+
* falso, y de eso se entera quien use la app.
|
|
822
|
+
*
|
|
823
|
+
* **Un juez que falla no tumba el turno**: es una opinión sobre trabajo ya hecho. Y
|
|
824
|
+
* sin puerto no se pregunta nada, así que `npm test` sigue sin modelo.
|
|
825
|
+
*/
|
|
826
|
+
if (opciones.juezDelTurno !== undefined && !reparar) {
|
|
827
|
+
try {
|
|
828
|
+
const veredictoDelJuez = await opciones.juezDelTurno({
|
|
829
|
+
objetivo: peticion,
|
|
830
|
+
respuesta: respuestaDeLaPasada,
|
|
831
|
+
hechos: {
|
|
832
|
+
// Ausente es «no corrió», que no es lo mismo que «salió mal»: el prompt lo dice.
|
|
833
|
+
...(veredicto === "verde" || veredicto === "rojo" ? { verificador: veredicto } : {}),
|
|
834
|
+
...(cortadoPorTope ? { escriturasSinResolver: true } : {}),
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
// `hayHumano: false` en esta versión: preguntar exige el `preguntar` de la consola,
|
|
838
|
+
// que hasta aquí no llega. Así la duda se DICE en vez de callarse, que es la
|
|
839
|
+
// degradación que `accionDelJuez` ya contempla para las tareas de fondo.
|
|
840
|
+
const accion = accionDelJuez(veredictoDelJuez, { hayHumano: false });
|
|
841
|
+
if (accion.tipo !== "nada") {
|
|
842
|
+
yield { tipo: "aviso", texto: `⚠ ${accion.texto}`, severidad: "aviso" };
|
|
662
843
|
}
|
|
663
844
|
}
|
|
664
845
|
catch (error) {
|
|
665
846
|
yield {
|
|
666
847
|
tipo: "aviso",
|
|
667
|
-
texto: `⚠ no se pudo
|
|
848
|
+
texto: `⚠ no se pudo consultar al juez del turno: ${error instanceof Error ? error.name : "error"}`,
|
|
668
849
|
severidad: "aviso",
|
|
669
850
|
};
|
|
670
851
|
}
|
|
671
852
|
}
|
|
672
|
-
if (errores === 0)
|
|
673
|
-
return;
|
|
674
|
-
// Rojo. Tres salidas, y solo una de ellas es «inténtalo otra vez».
|
|
675
|
-
//
|
|
676
|
-
// La huella son los ERRORES, no todos los hallazgos: un aviso que va y viene no dice
|
|
677
|
-
// nada de si el error se está arreglando. Y se compara con la del veredicto anterior
|
|
678
|
-
// y no con «¿bajó el número?»: dos errores distintos en vez de dos iguales también es
|
|
679
|
-
// avance, y un modelo que arregla uno y rompe otro no debe quedarse bloqueado como si
|
|
680
|
-
// no hubiera hecho nada.
|
|
681
|
-
const huella = hallazgos
|
|
682
|
-
.filter((h) => h.severidad === "error")
|
|
683
|
-
.map((h) => `${h.code}|${h.fichero ?? ""}|${h.linea ?? ""}`)
|
|
684
|
-
.sort()
|
|
685
|
-
.join("\n");
|
|
686
|
-
if (huella === huellaPrevia) {
|
|
687
|
-
// Corregir no cambió nada: el mismo error, en el mismo sitio. Seguir sería gastar
|
|
688
|
-
// el tope —y aprobaciones humanas— en repetir el mismo cambio.
|
|
689
|
-
yield {
|
|
690
|
-
tipo: "bloqueado",
|
|
691
|
-
motivo: "no-progreso",
|
|
692
|
-
explicacion: `el intento ${intento} dejó los mismos ${errores} error(es) en los mismos sitios`,
|
|
693
|
-
};
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
if (intento >= TOPE_REPARACIONES) {
|
|
697
|
-
yield {
|
|
698
|
-
tipo: "bloqueado",
|
|
699
|
-
motivo: "tope-reparaciones",
|
|
700
|
-
explicacion: `tras ${intento} intento(s) siguen ${errores} error(es); se deja como está para que lo mires`,
|
|
701
|
-
};
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
// Se intenta otra vez. Esta pasada NO cierra el turno: el `fin` y los avisos van al
|
|
705
|
-
// final del intento que viene, o del que corte.
|
|
706
|
-
huellaPrevia = huella;
|
|
707
|
-
ultimosHallazgos = hallazgos;
|
|
708
|
-
intento += 1;
|
|
709
|
-
reparar = true;
|
|
710
|
-
cerrarRonda = false;
|
|
711
853
|
}
|
|
712
854
|
/**
|
|
713
855
|
* La petición de reparación: los hallazgos tal cual, sin más interpretación.
|
|
@@ -718,7 +860,11 @@ export async function abrirSesionReal(opciones) {
|
|
|
718
860
|
* lo único que importa aquí: que no se invente nada para que el error desaparezca, que
|
|
719
861
|
* es justo lo que XOne no le va a reprochar y el simulador sí.
|
|
720
862
|
*/
|
|
721
|
-
|
|
863
|
+
// El objetivo es la petición TAL CUAL la escribió quien encargó el turno, no el
|
|
864
|
+
// `payloadInicial`: ése lleva además los hechos del proyecto precargados, que son
|
|
865
|
+
// contexto y no encargo — repetirlos en cada reparación sería pagarlos otra vez y
|
|
866
|
+
// enterrar la frase que de verdad hay que recordar.
|
|
867
|
+
const peticionDeReparacion = () => textoDeReparacion(ultimosHallazgos, observacionesVisuales, peticion);
|
|
722
868
|
// Dos bucles anidados y a propósito: el de dentro son las RONDAS de aprobación de una
|
|
723
869
|
// petición (una pausa termina la ronda, se reanuda con las decisiones); el de fuera son
|
|
724
870
|
// los INTENTOS de reparación, cada uno una petición nueva en el mismo hilo. Quién
|
|
@@ -903,8 +1049,42 @@ export async function abrirSesionReal(opciones) {
|
|
|
903
1049
|
...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),
|
|
904
1050
|
};
|
|
905
1051
|
};
|
|
1052
|
+
const fallos = crearRegistroDeFallos(raiz);
|
|
906
1053
|
return {
|
|
907
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* El turno, con el registro de fallos alrededor.
|
|
1056
|
+
*
|
|
1057
|
+
* **Se envuelve aquí y no dentro** porque `turno` tiene muchas salidas y lo que
|
|
1058
|
+
* interesa es la ÚNICA que importa para esto: que la excepción escape. Envolverlo por
|
|
1059
|
+
* dentro sería repartir la anotación por cada `throw`, que es cómo una contabilidad
|
|
1060
|
+
* deriva — el mismo argumento que ya hace que las pendientes se midan una vez al final.
|
|
1061
|
+
*
|
|
1062
|
+
* **Y se RELANZA siempre.** Esto anota y cuenta; no captura. Quien llama decide qué
|
|
1063
|
+
* hacer con el fallo, y tragárselo aquí convertiría un turno roto en uno que parece
|
|
1064
|
+
* que fue bien — exactamente lo contrario de lo que este registro existe para arreglar.
|
|
1065
|
+
*
|
|
1066
|
+
* Lo que se pinta es el texto PEGABLE, no un «ha fallado algo»: lo que hace útil un
|
|
1067
|
+
* error es la cadena de causas y el modelo en vigor, y hasta ahora había que ir a
|
|
1068
|
+
* buscarlos a mano.
|
|
1069
|
+
*/
|
|
1070
|
+
turno: async (peticion, piel, ...resto) => {
|
|
1071
|
+
pasosDelTurno.length = 0;
|
|
1072
|
+
try {
|
|
1073
|
+
return await turno(peticion, piel, ...resto);
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
const anotado = fallos.anotar({
|
|
1077
|
+
error,
|
|
1078
|
+
peticion,
|
|
1079
|
+
modelos: modelos.descripcion(),
|
|
1080
|
+
pasos: pasosDelTurno,
|
|
1081
|
+
});
|
|
1082
|
+
if (anotado !== undefined) {
|
|
1083
|
+
piel.linea(`\n⚠ el turno falló. Queda apuntado en ${anotado.ruta}\n${anotado.legible}\n`);
|
|
1084
|
+
}
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
908
1088
|
cancelar: () => cancelarEnCurso?.(),
|
|
909
1089
|
cerrar: () => {
|
|
910
1090
|
cerrada = true;
|