xonecode 0.8.4 → 0.8.13
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/ecoDeRazonamiento.js +229 -0
- package/dist/agent/config/ecoDeRazonamiento.js.map +1 -0
- package/dist/agent/config/modelos.js +34 -2
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/dispositivos/juezVisual.js +15 -1
- package/dist/agent/dispositivos/juezVisual.js.map +1 -1
- package/dist/agent/grafo/criticaVisual.js +14 -1
- package/dist/agent/grafo/criticaVisual.js.map +1 -1
- package/dist/agent/grafo/perfiles.js +20 -2
- package/dist/agent/grafo/perfiles.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +17 -1
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +10 -4
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/tareas/juezDeTarea.js +16 -1
- package/dist/agent/tareas/juezDeTarea.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 +359 -159
- 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/esfuerzo.js +23 -11
- 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/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,298 @@ 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
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
|
|
540
|
-
|
|
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
|
+
/**
|
|
587
|
+
* **Los artefactos salen DONDE se escribieron, no en un montón al final.**
|
|
588
|
+
*
|
|
589
|
+
* Se acumulaban y se vaciaban de golpe al agotarse la pasada, y con un turno de
|
|
590
|
+
* `device-controller` eso son dieciocho tarjetas seguidas DESPUÉS de la respuesta:
|
|
591
|
+
* entierran el resumen, que es lo único que la persona quería leer, y las separan
|
|
592
|
+
* del trabajo que las produjo — una captura suelta no dice nada, una captura justo
|
|
593
|
+
* detrás del comando que la sacó sí.
|
|
594
|
+
*
|
|
595
|
+
* Se drena aquí y no con `entrelazar` porque este bucle ya recorre la pasada: la
|
|
596
|
+
* cola de `core/entrelazar.ts` existe para intercalar lo que viene de OTRO proceso,
|
|
597
|
+
* y aquí el productor es el mismo turno. Añadir una cola sería una segunda forma de
|
|
598
|
+
* hacer lo mismo.
|
|
599
|
+
*
|
|
600
|
+
* El vaciado final se queda igual: recoge lo que se escriba DESPUÉS del último
|
|
601
|
+
* evento, que si no se perdería.
|
|
602
|
+
*/
|
|
603
|
+
for (const artefacto of artefactosDeLaPasada.splice(0))
|
|
604
|
+
yield { tipo: "artefacto", artefacto };
|
|
605
|
+
if (evento.tipo === "token")
|
|
606
|
+
respuestaDeLaPasada += evento.texto;
|
|
607
|
+
if (evento.tipo === "tool")
|
|
608
|
+
pasosDelTurno.push(evento.nombre);
|
|
609
|
+
yield evento;
|
|
610
|
+
}
|
|
611
|
+
// Lo que se haya escrito DESPUÉS del último evento: el bucle de arriba ya fue
|
|
612
|
+
// sacando los demás según se escribían. Va antes de la decisión de cierre y antes
|
|
613
|
+
// del veredicto porque es trabajo TERMINADO de esta pasada — anunciarlo después del
|
|
614
|
+
// `fin` lo pintaría fuera del turno, que es el mismo motivo por el que el
|
|
615
|
+
// verificador está cosido aquí dentro.
|
|
541
616
|
for (const artefacto of artefactosDeLaPasada.splice(0))
|
|
542
617
|
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
618
|
/**
|
|
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—.
|
|
619
|
+
* El verificador y el crítico, en su propio generador.
|
|
615
620
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
621
|
+
* Se extrae por una razón de FLUJO y no de estética: este cuerpo tiene CUATRO
|
|
622
|
+
* `return` que significan «no hay nada que reparar» —escrituras pendientes, una
|
|
623
|
+
* ronda que no escribió, sin verificador, un verificador que revienta—, y todos
|
|
624
|
+
* ellos son el final feliz del turno. Con el juez escrito detrás de ellos no se
|
|
625
|
+
* llamaba nunca en esos casos, que son justo los que más falta hace mirar: un turno
|
|
626
|
+
* que no escribió nada es el candidato número uno a no haber cumplido el encargo.
|
|
620
627
|
*
|
|
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.
|
|
628
|
+
* Metido aquí, esos `return` terminan solo ESTE generador y el juez de abajo corre
|
|
629
|
+
* igual. Comprobado con el test del cableado, que sin esto no llamaba al juez.
|
|
629
630
|
*/
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
631
|
+
async function* verificarYCriticar() {
|
|
632
|
+
if ((await leerPendientes()).lista.length > 0) {
|
|
633
|
+
// Quedan escrituras por aprobar. Si el bucle va a seguir —hay quien apruebe y no se
|
|
634
|
+
// ha agotado el tope de rondas— esta pasada NO es el final del turno y no cierra.
|
|
635
|
+
// Son las mismas dos condiciones del `break` del bucle, y tienen que serlo: si
|
|
636
|
+
// divergen, un turno se queda sin `fin` o cierra dos veces.
|
|
637
|
+
cerrarRonda = !(ronda < topeDeRondas && opciones.pedirAprobacion !== undefined);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
cerrarRonda = true;
|
|
641
|
+
const cambios = (await instantanea.cambios()).filter((c) => c.clase !== "borrado" && !c.ruta.startsWith(".xonecode/") && c.ruta !== ".xonecode");
|
|
642
|
+
rondaEscribio = cambios.length > 0;
|
|
643
|
+
if (!rondaEscribio) {
|
|
644
|
+
// No hay nada que verificar, y eso NO es un verde. Se DICE, porque este motivo
|
|
645
|
+
// acaba en la tarjeta de una tarea aparcada y «no se sabe» tiene que distinguirse
|
|
646
|
+
// de «el simulador no está» y de «el turno se cortó antes de llegar».
|
|
647
|
+
motivoSinVerificar = "el turno no escribió ningún fichero del proyecto";
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (opciones.verifier === undefined) {
|
|
651
|
+
motivoSinVerificar = "esta ejecución no tiene verificador";
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
yield { tipo: "fase", fase: "verificando" };
|
|
655
|
+
let informe;
|
|
640
656
|
try {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
657
|
+
informe = await opciones.verifier.verificar(raiz);
|
|
658
|
+
}
|
|
659
|
+
catch (e) {
|
|
660
|
+
// Que no esté el binario NO es un fallo del proyecto, y se dice como tal. El aviso
|
|
661
|
+
// determinista del final saldrá igualmente: «no ha corrido» sigue siendo verdad.
|
|
662
|
+
motivoSinVerificar = e instanceof Error ? e.message : String(e);
|
|
663
|
+
yield { tipo: "aviso", texto: `⚠ no se pudo verificar: ${motivoSinVerificar}`, severidad: "aviso" };
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// Los hallazgos se reparten entre los ficheros que ESTE turno tocó y los demás. El
|
|
667
|
+
// simulador mira el proyecto entero —es su API—, y un error que ya estaba en un
|
|
668
|
+
// fichero que el agente no abrió no es del agente. Un hallazgo sin fichero no se
|
|
669
|
+
// puede atribuir: se enseña con los del turno, que es el lado conservador.
|
|
670
|
+
const tocados = new Set(cambios.map((c) => resolverRuta(raiz, c.ruta)));
|
|
671
|
+
const delTurno = informe.hallazgos.filter((h) => h.fichero === undefined || tocados.has(resolverRuta(h.fichero)));
|
|
672
|
+
const preexistentes = informe.hallazgos.length - delTurno.length;
|
|
673
|
+
const errores = delTurno.filter((h) => h.severidad === "error").length;
|
|
674
|
+
const hallazgos = delTurno.map((h) => ({
|
|
675
|
+
code: h.code,
|
|
676
|
+
severidad: h.severidad,
|
|
677
|
+
mensaje: h.mensaje,
|
|
678
|
+
...(h.fichero === undefined ? {} : { fichero: relative(raiz, h.fichero) }),
|
|
679
|
+
...(h.linea === undefined ? {} : { linea: h.linea }),
|
|
680
|
+
}));
|
|
681
|
+
// Lo mismo que va al evento, apuntado para el retorno: NO se recalcula ni se
|
|
682
|
+
// re-parsea de la bitácora, que es cómo dos copias de una cuenta acaban discrepando.
|
|
683
|
+
veredicto = errores === 0 ? "verde" : "rojo";
|
|
684
|
+
hallazgosDelTurno = hallazgos;
|
|
685
|
+
// El reparto entero, no solo su mitad. Aquí sí se apunta el cero: el evento lo omite
|
|
686
|
+
// porque una línea de consola que dice «y 0 más» no dice nada, pero quien recibe
|
|
687
|
+
// `hallazgos` necesita saber que la lista está filtrada — y con cero también.
|
|
688
|
+
preexistentesDelTurno = preexistentes;
|
|
689
|
+
yield {
|
|
690
|
+
tipo: "verificacion",
|
|
691
|
+
verde: errores === 0,
|
|
692
|
+
errores,
|
|
693
|
+
avisos: delTurno.length - errores,
|
|
694
|
+
hallazgos,
|
|
695
|
+
...(preexistentes > 0 ? { preexistentes } : {}),
|
|
696
|
+
};
|
|
697
|
+
/**
|
|
698
|
+
* **El crítico visual, enganchado AQUÍ y no en el prompt del orquestador.**
|
|
699
|
+
*
|
|
700
|
+
* Ésta es la diferencia entre que la crítica ocurra y que ocurra cuando el modelo se
|
|
701
|
+
* acuerde. Es el mismo punto y el mismo mecanismo que el resto del lazo —y el mismo que
|
|
702
|
+
* el `RubricMiddleware` de deepagents: enganchar donde el agente iba a terminar y
|
|
703
|
+
* devolver el feedback como mensaje de USUARIO en el mismo hilo—.
|
|
704
|
+
*
|
|
705
|
+
* **No conduce nada**: mira las capturas que este turno ya dejó. Navegar hasta una
|
|
706
|
+
* pantalla necesita un agente (medido: el control que lleva a la Calculadora vive en un
|
|
707
|
+
* cajón cerrado y hay que abrirlo mirando el árbol), pero MIRAR lo ya fotografiado no.
|
|
708
|
+
* Sin capturas no opina, que es honesto: no hay nada que ver.
|
|
709
|
+
*
|
|
710
|
+
* Y que no se pueda preguntar NO es un rojo: se dice como aviso y el turno sigue, igual
|
|
711
|
+
* que con el binario del simulador.
|
|
712
|
+
*
|
|
713
|
+
* **Límite declarado**: esto cuelga del mismo bloque que el verificador, así que solo
|
|
714
|
+
* corre en un turno que ESCRIBIÓ ficheros del proyecto. Un turno de pura inspección
|
|
715
|
+
* —«ve a esa pantalla y dime qué ves»— no pasa por aquí, y no debe: la pregunta que
|
|
716
|
+
* contesta este enganche es «¿quedó bien lo que acabas de escribir?». Para mirar sin
|
|
717
|
+
* escribir está la tool `xone_critica_visual`, que el orquestador invoca cuando quiere.
|
|
718
|
+
*/
|
|
719
|
+
if (tocaCriticarPantalla({
|
|
720
|
+
hayCritico: opciones.criticaVisual !== undefined,
|
|
721
|
+
capturas: capturasDelTurno.length,
|
|
722
|
+
yaDisparo: visualYaDisparo,
|
|
723
|
+
intento,
|
|
724
|
+
})) {
|
|
725
|
+
visualYaDisparo = true;
|
|
726
|
+
// La ÚLTIMA: es el estado más reciente de la pantalla, y las anteriores pueden ser de
|
|
727
|
+
// antes del cambio que este turno acaba de hacer.
|
|
728
|
+
const captura = capturasDelTurno[capturasDelTurno.length - 1];
|
|
729
|
+
try {
|
|
730
|
+
const bytes = readFileSync(join(carpetaDeArtefactos, captura.nombre));
|
|
731
|
+
const visual = await opciones.criticaVisual({ base64: bytes.toString("base64"), mime: captura.mime ?? "image/png" }, captura.nombre);
|
|
732
|
+
if (visual.veredicto === "rojo" && visual.observaciones.length > 0) {
|
|
733
|
+
observacionesVisuales = visual.observaciones;
|
|
734
|
+
yield {
|
|
735
|
+
tipo: "aviso",
|
|
736
|
+
texto: `⚠ la captura de esta sesión enseña ${visual.observaciones.length} defecto(s) de pantalla`,
|
|
737
|
+
severidad: "aviso",
|
|
738
|
+
};
|
|
739
|
+
/**
|
|
740
|
+
* **Con el simulador en VERDE, el crítico REPORTA y no dispara reparación.**
|
|
741
|
+
*
|
|
742
|
+
* Antes sí la disparaba, y ésa era la tercera puerta de la misma deriva. MEDIDO
|
|
743
|
+
* dos veces en turnos reales: se pidió «arregla el error al ejecutar la app», se
|
|
744
|
+
* arregló, el conductor MIDIÓ que arranca y llega a Menu —o sea, objetivo
|
|
745
|
+
* cumplido y comprobado— y aun así el crítico veía cuatro textos recortados en
|
|
746
|
+
* otra pantalla, rotos desde antes, y el harness abría otra ronda «de
|
|
747
|
+
* reparación». El turno se iba a rediseñar el menú.
|
|
748
|
+
*
|
|
749
|
+
* Verde del simulador + objetivo cumplido no es un turno a medias: es un turno
|
|
750
|
+
* terminado con algo que decir. Las observaciones ya salen como `aviso` y van en
|
|
751
|
+
* la respuesta; quien decide si eso se toca es la persona, que es la misma regla
|
|
752
|
+
* de `preexistentes` — no se repara lo que no rompiste.
|
|
753
|
+
*
|
|
754
|
+
* En ROJO no cambia nada: las observaciones se suman a la petición que iba a
|
|
755
|
+
* salir igualmente, con el objetivo delante (`textoDeReparacion`), y no se gasta
|
|
756
|
+
* una vuelta extra.
|
|
757
|
+
*
|
|
758
|
+
* **El precio, declarado**: un turno que SÍ rompió el layout y dejó el simulador
|
|
759
|
+
* en verde ya no se autocorrige. Lo que queda es el aviso y lo que el propio
|
|
760
|
+
* agente haga dentro de su ronda —tiene la tool—, y eso se ve. Se prefiere eso a
|
|
761
|
+
* una ronda que arregla lo que nadie pidió, que es lo que pasaba.
|
|
762
|
+
*
|
|
763
|
+
* **Y un hueco de TEST, dicho y no escondido**: esta rama no tiene prueba de
|
|
764
|
+
* turno. El arnés de `turnoReal.test.ts` no sabe producir una captura —el
|
|
765
|
+
* crítico solo entra con un artefacto de imagen en `capturasDelTurno` y además
|
|
766
|
+
* LEE el fichero del disco—, así que atarlo pide cablear eso primero. Lo que sí
|
|
767
|
+
* está probado es `tocaCriticarPantalla` (cuándo entra el crítico) y el objetivo
|
|
768
|
+
* dentro de `textoDeReparacion` (qué recibe el agente cuando sí hay ronda).
|
|
769
|
+
*/
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
645
773
|
yield {
|
|
646
774
|
tipo: "aviso",
|
|
647
|
-
texto: `⚠
|
|
775
|
+
texto: `⚠ no se pudo criticar la pantalla: ${error instanceof Error ? error.name : "error"}`,
|
|
648
776
|
severidad: "aviso",
|
|
649
777
|
};
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (errores === 0)
|
|
781
|
+
return;
|
|
782
|
+
// Rojo. Tres salidas, y solo una de ellas es «inténtalo otra vez».
|
|
783
|
+
//
|
|
784
|
+
// La huella son los ERRORES, no todos los hallazgos: un aviso que va y viene no dice
|
|
785
|
+
// nada de si el error se está arreglando. Y se compara con la del veredicto anterior
|
|
786
|
+
// y no con «¿bajó el número?»: dos errores distintos en vez de dos iguales también es
|
|
787
|
+
// avance, y un modelo que arregla uno y rompe otro no debe quedarse bloqueado como si
|
|
788
|
+
// no hubiera hecho nada.
|
|
789
|
+
const huella = hallazgos
|
|
790
|
+
.filter((h) => h.severidad === "error")
|
|
791
|
+
.map((h) => `${h.code}|${h.fichero ?? ""}|${h.linea ?? ""}`)
|
|
792
|
+
.sort()
|
|
793
|
+
.join("\n");
|
|
794
|
+
if (huella === huellaPrevia) {
|
|
795
|
+
// Corregir no cambió nada: el mismo error, en el mismo sitio. Seguir sería gastar
|
|
796
|
+
// el tope —y aprobaciones humanas— en repetir el mismo cambio.
|
|
797
|
+
yield {
|
|
798
|
+
tipo: "bloqueado",
|
|
799
|
+
motivo: "no-progreso",
|
|
800
|
+
explicacion: `el intento ${intento} dejó los mismos ${errores} error(es) en los mismos sitios`,
|
|
801
|
+
};
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (intento >= TOPE_REPARACIONES) {
|
|
805
|
+
yield {
|
|
806
|
+
tipo: "bloqueado",
|
|
807
|
+
motivo: "tope-reparaciones",
|
|
808
|
+
explicacion: `tras ${intento} intento(s) siguen ${errores} error(es); se deja como está para que lo mires`,
|
|
809
|
+
};
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
// Se intenta otra vez. Esta pasada NO cierra el turno: el `fin` y los avisos van al
|
|
813
|
+
// final del intento que viene, o del que corte.
|
|
814
|
+
huellaPrevia = huella;
|
|
815
|
+
ultimosHallazgos = hallazgos;
|
|
816
|
+
intento += 1;
|
|
817
|
+
reparar = true;
|
|
818
|
+
cerrarRonda = false;
|
|
819
|
+
}
|
|
820
|
+
yield* verificarYCriticar();
|
|
821
|
+
/**
|
|
822
|
+
* **El turno se acaba aquí. ¿Pero hizo lo que se le pidió?**
|
|
823
|
+
*
|
|
824
|
+
* Ningún `if` contesta eso, y es la pregunta de quien lo encargó. MEDIDO en turnos
|
|
825
|
+
* reales, tres veces en la misma sesión: «el especialista se ha vuelto a cortar antes
|
|
826
|
+
* de darme el informe», «las dos tareas que lancé no llegaron a ejecutarse», «la
|
|
827
|
+
* devolución del verificador ha llegado cortada». En los tres el turno cerró y el
|
|
828
|
+
* resumen sonaba a trabajo terminado.
|
|
829
|
+
*
|
|
830
|
+
* Va DESPUÉS del verificador y FUERA de él a propósito: sus cuatro `return` son
|
|
831
|
+
* finales felices del turno —incluido «no escribió nada», que es el candidato número
|
|
832
|
+
* uno a no haber cumplido—, y el juez tiene que llegar a todos.
|
|
833
|
+
*
|
|
834
|
+
* **Y no se juzga un turno que va a seguir**: con `reparar` puesto viene otra pasada,
|
|
835
|
+
* así que preguntar aquí sería juzgar un trabajo a medias y avisar de algo que está a
|
|
836
|
+
* punto de arreglarse solo.
|
|
837
|
+
*
|
|
838
|
+
* El reparto es el de las tareas (`core/entrega.ts`): los HECHOS los mide el código y
|
|
839
|
+
* se le dan hechos, y lo único que se le pregunta es el juicio. Lo que no se entiende
|
|
840
|
+
* es `dudoso`, nunca `cumplido`: decir que se cumplió sin serlo cierra el turno en
|
|
841
|
+
* falso, y de eso se entera quien use la app.
|
|
842
|
+
*
|
|
843
|
+
* **Un juez que falla no tumba el turno**: es una opinión sobre trabajo ya hecho. Y
|
|
844
|
+
* sin puerto no se pregunta nada, así que `npm test` sigue sin modelo.
|
|
845
|
+
*/
|
|
846
|
+
if (opciones.juezDelTurno !== undefined && !reparar) {
|
|
847
|
+
try {
|
|
848
|
+
const veredictoDelJuez = await opciones.juezDelTurno({
|
|
849
|
+
objetivo: peticion,
|
|
850
|
+
respuesta: respuestaDeLaPasada,
|
|
851
|
+
hechos: {
|
|
852
|
+
// Ausente es «no corrió», que no es lo mismo que «salió mal»: el prompt lo dice.
|
|
853
|
+
...(veredicto === "verde" || veredicto === "rojo" ? { verificador: veredicto } : {}),
|
|
854
|
+
...(cortadoPorTope ? { escriturasSinResolver: true } : {}),
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
// `hayHumano: false` en esta versión: preguntar exige el `preguntar` de la consola,
|
|
858
|
+
// que hasta aquí no llega. Así la duda se DICE en vez de callarse, que es la
|
|
859
|
+
// degradación que `accionDelJuez` ya contempla para las tareas de fondo.
|
|
860
|
+
const accion = accionDelJuez(veredictoDelJuez, { hayHumano: false });
|
|
861
|
+
if (accion.tipo !== "nada") {
|
|
862
|
+
yield { tipo: "aviso", texto: `⚠ ${accion.texto}`, severidad: "aviso" };
|
|
662
863
|
}
|
|
663
864
|
}
|
|
664
865
|
catch (error) {
|
|
665
866
|
yield {
|
|
666
867
|
tipo: "aviso",
|
|
667
|
-
texto: `⚠ no se pudo
|
|
868
|
+
texto: `⚠ no se pudo consultar al juez del turno: ${error instanceof Error ? error.name : "error"}`,
|
|
668
869
|
severidad: "aviso",
|
|
669
870
|
};
|
|
670
871
|
}
|
|
671
872
|
}
|
|
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
873
|
}
|
|
712
874
|
/**
|
|
713
875
|
* La petición de reparación: los hallazgos tal cual, sin más interpretación.
|
|
@@ -718,7 +880,11 @@ export async function abrirSesionReal(opciones) {
|
|
|
718
880
|
* lo único que importa aquí: que no se invente nada para que el error desaparezca, que
|
|
719
881
|
* es justo lo que XOne no le va a reprochar y el simulador sí.
|
|
720
882
|
*/
|
|
721
|
-
|
|
883
|
+
// El objetivo es la petición TAL CUAL la escribió quien encargó el turno, no el
|
|
884
|
+
// `payloadInicial`: ése lleva además los hechos del proyecto precargados, que son
|
|
885
|
+
// contexto y no encargo — repetirlos en cada reparación sería pagarlos otra vez y
|
|
886
|
+
// enterrar la frase que de verdad hay que recordar.
|
|
887
|
+
const peticionDeReparacion = () => textoDeReparacion(ultimosHallazgos, observacionesVisuales, peticion);
|
|
722
888
|
// Dos bucles anidados y a propósito: el de dentro son las RONDAS de aprobación de una
|
|
723
889
|
// petición (una pausa termina la ronda, se reanuda con las decisiones); el de fuera son
|
|
724
890
|
// los INTENTOS de reparación, cada uno una petición nueva en el mismo hilo. Quién
|
|
@@ -903,8 +1069,42 @@ export async function abrirSesionReal(opciones) {
|
|
|
903
1069
|
...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),
|
|
904
1070
|
};
|
|
905
1071
|
};
|
|
1072
|
+
const fallos = crearRegistroDeFallos(raiz);
|
|
906
1073
|
return {
|
|
907
|
-
|
|
1074
|
+
/**
|
|
1075
|
+
* El turno, con el registro de fallos alrededor.
|
|
1076
|
+
*
|
|
1077
|
+
* **Se envuelve aquí y no dentro** porque `turno` tiene muchas salidas y lo que
|
|
1078
|
+
* interesa es la ÚNICA que importa para esto: que la excepción escape. Envolverlo por
|
|
1079
|
+
* dentro sería repartir la anotación por cada `throw`, que es cómo una contabilidad
|
|
1080
|
+
* deriva — el mismo argumento que ya hace que las pendientes se midan una vez al final.
|
|
1081
|
+
*
|
|
1082
|
+
* **Y se RELANZA siempre.** Esto anota y cuenta; no captura. Quien llama decide qué
|
|
1083
|
+
* hacer con el fallo, y tragárselo aquí convertiría un turno roto en uno que parece
|
|
1084
|
+
* que fue bien — exactamente lo contrario de lo que este registro existe para arreglar.
|
|
1085
|
+
*
|
|
1086
|
+
* Lo que se pinta es el texto PEGABLE, no un «ha fallado algo»: lo que hace útil un
|
|
1087
|
+
* error es la cadena de causas y el modelo en vigor, y hasta ahora había que ir a
|
|
1088
|
+
* buscarlos a mano.
|
|
1089
|
+
*/
|
|
1090
|
+
turno: async (peticion, piel, ...resto) => {
|
|
1091
|
+
pasosDelTurno.length = 0;
|
|
1092
|
+
try {
|
|
1093
|
+
return await turno(peticion, piel, ...resto);
|
|
1094
|
+
}
|
|
1095
|
+
catch (error) {
|
|
1096
|
+
const anotado = fallos.anotar({
|
|
1097
|
+
error,
|
|
1098
|
+
peticion,
|
|
1099
|
+
modelos: modelos.descripcion(),
|
|
1100
|
+
pasos: pasosDelTurno,
|
|
1101
|
+
});
|
|
1102
|
+
if (anotado !== undefined) {
|
|
1103
|
+
piel.linea(`\n⚠ el turno falló. Queda apuntado en ${anotado.ruta}\n${anotado.legible}\n`);
|
|
1104
|
+
}
|
|
1105
|
+
throw error;
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
908
1108
|
cancelar: () => cancelarEnCurso?.(),
|
|
909
1109
|
cerrar: () => {
|
|
910
1110
|
cerrada = true;
|