xonecode 0.8.23 → 0.8.26

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 (44) hide show
  1. package/apps/web/dist/assets/{index-CtYEobZI.css → index-DKZqgHtg.css} +1 -1
  2. package/apps/web/dist/assets/{index-mTaG_gr6.js → index-LdUr7EoT.js} +63 -63
  3. package/apps/web/dist/assets/{php-BEI3vU5S.js → php-Cg-eWGpQ.js} +1 -1
  4. package/apps/web/dist/assets/{ruby-DCYozE_k.js → ruby-EcV7Hon3.js} +1 -1
  5. package/apps/web/dist/index.html +2 -2
  6. package/dist/agent/dispositivos/juezVisual.js +88 -4
  7. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  8. package/dist/agent/grafo/artefactosEnDisco.js +15 -4
  9. package/dist/agent/grafo/artefactosEnDisco.js.map +1 -1
  10. package/dist/agent/grafo/criticaVisual.js +134 -24
  11. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  12. package/dist/agent/grafo/proyecto.js +101 -22
  13. package/dist/agent/grafo/proyecto.js.map +1 -1
  14. package/dist/agent/grafo/xoneAgent.js +1 -1
  15. package/dist/agent/grafo/xoneAgent.js.map +1 -1
  16. package/dist/agent/sesiones/checkpointer.js +183 -1
  17. package/dist/agent/sesiones/checkpointer.js.map +1 -1
  18. package/dist/agent/subagentes/agentesEnDisco.js +3 -1
  19. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  20. package/dist/cli/consola.js.map +1 -1
  21. package/dist/cli/main.js +9 -0
  22. package/dist/cli/main.js.map +1 -1
  23. package/dist/cli/podar.js +112 -0
  24. package/dist/cli/podar.js.map +1 -0
  25. package/dist/core/actos.js.map +1 -1
  26. package/dist/core/artefactos.js +47 -0
  27. package/dist/core/artefactos.js.map +1 -1
  28. package/dist/core/hotswap.js +71 -0
  29. package/dist/core/hotswap.js.map +1 -0
  30. package/dist/core/podaDeCheckpoint.js +74 -0
  31. package/dist/core/podaDeCheckpoint.js.map +1 -0
  32. package/dist/core/shellDeAgente.js +12 -0
  33. package/dist/core/shellDeAgente.js.map +1 -1
  34. package/dist/web/servidor/arranque.js +53 -5
  35. package/dist/web/servidor/arranque.js.map +1 -1
  36. package/dist/web/servidor/consolaWeb.js +42 -5
  37. package/dist/web/servidor/consolaWeb.js.map +1 -1
  38. package/dist/web/servidor/pielWeb.js +13 -4
  39. package/dist/web/servidor/pielWeb.js.map +1 -1
  40. package/dist/web/servidor/vestibulo.js +22 -0
  41. package/dist/web/servidor/vestibulo.js.map +1 -1
  42. package/package.json +1 -1
  43. package/skills/xone-hotswap/SKILL.md +8 -4
  44. package/skills/xone-hotswap/scripts/xone-hotswap +42 -7
@@ -7,8 +7,8 @@
7
7
  <!-- El icono oficial, servido por Vite desde `iconos/`. Es el mismo fichero que la
8
8
  marca de la barra superior: dos copias del logo acaban siendo dos logos. -->
9
9
  <link rel="icon" type="image/png" href="./iconos/xonecode.png" />
10
- <script type="module" crossorigin src="./assets/index-mTaG_gr6.js"></script>
11
- <link rel="stylesheet" crossorigin href="./assets/index-CtYEobZI.css">
10
+ <script type="module" crossorigin src="./assets/index-LdUr7EoT.js"></script>
11
+ <link rel="stylesheet" crossorigin href="./assets/index-DKZqgHtg.css">
12
12
  </head>
13
13
  <!-- Sin el atributo aquí: el oscuro es SOLO del splash (el registro de entorno y
14
14
  modelo), no de la consola entera — `App.tsx` lo pone y lo quita él mismo, en el
@@ -57,17 +57,82 @@ export const PROMPT_VISUAL = [
57
57
  "",
58
58
  'Contesta SOLO este JSON: {"veredicto":"verde"|"rojo","hallazgos":["lo que se ve", …],"necesito":["Coleccion", …]}',
59
59
  ].join("\n");
60
+ /**
61
+ * Lo que se le pide CUANDO hay maqueta delante, y existe por una medida.
62
+ *
63
+ * **El modelo ve las imágenes de sobra; lo que decide qué ve es qué se le pide describir.**
64
+ * Sobre la calculadora de MyAllXOne el crítico acertó que una captura era el login y no la
65
+ * pantalla pedida, y leyó los rótulos `AP_EXPRESION`/`AP_RESULTADO` cortados contra el borde.
66
+ * Luego dio VERDE sobre una pantalla cuyas teclas eran rectángulos donde la maqueta tenía
67
+ * píldoras y cuyo resultado era 1,4 veces la expresión donde la maqueta pedía 2,7. No falló:
68
+ * `PROMPT_VISUAL` le dice literalmente que NO opine de la paleta ni de los gustos, y la
69
+ * fidelidad cae entera en ese conjunto excluido. La prueba de que era la pregunta y no la
70
+ * vista: el orquestador llegó a escribirle «las teclas llevan esquinas redondeadas (radio
71
+ * alto)» y el crítico no protestó, porque no se le pidió comparar.
72
+ *
73
+ * Tres cosas que no son de forma:
74
+ *
75
+ * - **Se nombra QUÉ comparar** —proporciones, jerarquía de tamaños, forma y alineación— en
76
+ * vez de pedir «¿se parecen?». Una pregunta abierta devuelve impresiones; ésta devuelve
77
+ * sitios.
78
+ * - **El COLOR se compara, el GUSTO no.** «El botón es coral donde la maqueta lo pone gris»
79
+ * es un hecho comprobable contra una imagen que está delante; «la paleta es fría» sigue
80
+ * siendo una opinión y sigue fuera. Sin referencia no se puede distinguir, y por eso allí
81
+ * se excluyen los dos.
82
+ * - **Se conserva «no diagnostiques la causa»**, que está medido seis veces: describe un
83
+ * texto CORTADO como «girado 180°». Acierta DÓNDE y falla en el PORQUÉ, y eso no cambia
84
+ * porque haya una maqueta al lado.
85
+ *
86
+ * Y se conserva que una diferencia deliberada no es un defecto: una plataforma no da todo lo
87
+ * que da un navegador, así que lo que se pide es lo que SE VE distinto, no un veredicto de
88
+ * si estuvo bien decidido.
89
+ */
90
+ export const PROMPT_VISUAL_CON_REFERENCIA = [
91
+ "Eres un revisor VISUAL de la pantalla de una app móvil. Te doy DOS imágenes:",
92
+ "1) la MAQUETA: cómo tendría que verse.",
93
+ "2) la CAPTURA del aparato: cómo se ve de verdad.",
94
+ "",
95
+ "Di en qué se DIFERENCIAN, sin diagnosticar: describe lo que se ve, no la causa ni cómo",
96
+ "arreglarlo. Quien lo lea tiene el código delante y tú no.",
97
+ "",
98
+ "Compara, y nombra el control concreto en cada diferencia:",
99
+ "- FORMA: esquinas (redondeadas, en píldora, en círculo o rectas), bordes, sombras.",
100
+ "- TAMAÑOS Y JERARQUÍA: qué es más grande que qué, y cuánto. Si en la maqueta un texto es",
101
+ " el doble que otro y en la captura son casi iguales, eso es una diferencia.",
102
+ "- COLOR: solo comparado con la maqueta —«es gris donde la maqueta lo pone coral»—. NO",
103
+ " opines de si la paleta te gusta.",
104
+ "- COLOCACIÓN: alineación, márgenes, huecos, orden de los elementos, qué falta y qué sobra.",
105
+ "- Y lo de siempre: texto cortado, ilegible, solapado o fuera de la pantalla.",
106
+ "",
107
+ "No toda diferencia es un error: una app nativa no puede dar todo lo que da un navegador.",
108
+ "Di lo que VES distinto y deja que lo valore quien lee.",
109
+ "",
110
+ "Si no ves ninguna diferencia que merezca contarse, dilo con la lista vacía.",
111
+ "",
112
+ "Si para dictaminar necesitas ver OTRA pantalla, pídela en `necesito` por el NOMBRE de su",
113
+ "colección y nada más (por ejemplo «Productos»), no con una frase. Alguien irá a por ella.",
114
+ "Si con lo que tienes te basta, déjalo vacío.",
115
+ "",
116
+ 'Contesta SOLO este JSON: {"veredicto":"verde"|"rojo","hallazgos":["lo que se ve", …],"necesito":["Coleccion", …]}',
117
+ ].join("\n");
60
118
  /**
61
119
  * Le enseña la captura al modelo y devuelve su veredicto.
62
120
  *
63
121
  * `invocar` entra por parámetro, así que `npm test` no le pregunta a ningún modelo — el mismo
64
122
  * molde que `JuezDeTareaPort`.
65
123
  */
66
- export async function juzgarPantalla(imagen, contexto, invocar) {
67
- const prompt = `${PROMPT_VISUAL}\n\nLa captura es de la pantalla «${contexto.pantalla}».`;
124
+ export async function juzgarPantalla(imagen, contexto, invocar,
125
+ /**
126
+ * La maqueta, si la hay. **Cuarto parámetro y opcional**, así los dos llamadores de
127
+ * `cli/` no cambian: lo que hace un `xonecode` de diagnóstico es mirar una pantalla, no
128
+ * compararla contra nada.
129
+ */
130
+ referencia) {
131
+ const base = referencia === undefined ? PROMPT_VISUAL : PROMPT_VISUAL_CON_REFERENCIA;
132
+ const prompt = `${base}\n\nLa captura es de la pantalla «${contexto.pantalla}».`;
68
133
  let texto;
69
134
  try {
70
- texto = await invocar(PAPEL_DEL_JUEZ_VISUAL, prompt, imagen);
135
+ texto = await invocar(PAPEL_DEL_JUEZ_VISUAL, prompt, imagen, referencia);
71
136
  }
72
137
  catch (error) {
73
138
  throw new ErrorDelJuezVisual(error instanceof Error ? error.message : String(error));
@@ -116,7 +181,7 @@ export async function juzgarPantalla(imagen, contexto, invocar) {
116
181
  * el cuerpo remoto y con él cabeceras y claves redactadas.
117
182
  */
118
183
  export function invocarVisualConModelos(modelos) {
119
- return async (papel, prompt, imagen) => {
184
+ return async (papel, prompt, imagen, referencia) => {
120
185
  let modelo;
121
186
  try {
122
187
  modelo = modelos.paraPapel(papel);
@@ -146,10 +211,29 @@ export function invocarVisualConModelos(modelos) {
146
211
  * clase de fuga que un argumento de tool en un evento. Lo que la persona tiene que
147
212
  * leer es lo que el harness decide contar con ese veredicto, no el JSON.
148
213
  */
214
+ /**
215
+ * **Cada imagen va DETRÁS de la línea que dice cuál es.** Con dos adjuntas y el prompt
216
+ * describiéndolas arriba, cuál es la maqueta y cuál el aparato dependería del orden en
217
+ * que el proveedor las numere — y eso no es un contrato, es una suposición. Una
218
+ * etiqueta pegada a cada una lo vuelve un dato. Si se invirtieran, el crítico contaría
219
+ * las diferencias al revés y se leerían igual de bien: es el fallo mudo de siempre.
220
+ *
221
+ * El orden es el del prompt: primero la MAQUETA, después la CAPTURA.
222
+ */
149
223
  const respuesta = await modelo.invoke([
150
224
  new HumanMessage({
151
225
  content: [
152
226
  { type: "text", text: prompt },
227
+ ...(referencia === undefined
228
+ ? []
229
+ : [
230
+ { type: "text", text: "MAQUETA (cómo tendría que verse):" },
231
+ {
232
+ type: "image_url",
233
+ image_url: { url: `data:${referencia.mime};base64,${referencia.base64}` },
234
+ },
235
+ { type: "text", text: "CAPTURA del aparato (cómo se ve de verdad):" },
236
+ ]),
153
237
  { type: "image_url", image_url: { url: `data:${imagen.mime};base64,${imagen.base64}` } },
154
238
  ],
155
239
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"juezVisual.js","sourceRoot":"","sources":["../../../src/agent/dispositivos/juezVisual.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"juezVisual.js","sourceRoot":"","sources":["../../../src/agent/dispositivos/juezVisual.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { readFile, realpath, stat } from "node:fs/promises";
25
25
  import { join, sep } from "node:path";
26
- import { RUTA_ARTEFACTOS, carpetaDeArtefactosDeSesion, esRutaDeArtefacto, mimeDeArtefacto, } from "../../core/artefactos.js";
26
+ import { RUTA_ARTEFACTOS, carpetaDeArtefactosDeSesion, esRutaDeArtefacto, mimeDeArtefacto, nombreDeArtefacto, } from "../../core/artefactos.js";
27
27
  import { leerContenidoDeFichero } from "./arbolDeProyecto.js";
28
28
  /**
29
29
  * Bytes que la ruta HTTP sirve como mucho.
@@ -116,7 +116,7 @@ export async function leerArtefactoDeSesion(raiz, sesion, nombre) {
116
116
  * y por eso lo pide ella a `mimeDeArtefacto`.
117
117
  */
118
118
  function mimeDeImagenDeArtefacto(nombre) {
119
- const mime = mimeDeArtefacto(nombre);
119
+ const mime = mimeDeArtefacto(nombreDeArtefacto(nombre));
120
120
  return mime !== undefined && mime.startsWith("image/") ? mime : undefined;
121
121
  }
122
122
  /** Para la RUTA HTTP: los bytes, sin recortar y sin base64. Ver `TOPE_DE_ARTEFACTO`. */
@@ -133,7 +133,18 @@ export async function leerArtefactoCrudo(raiz, sesion, nombre) {
133
133
  // comprobarlo después de `readFile` no protegería de nada.
134
134
  if (info.size > TOPE_DE_ARTEFACTO)
135
135
  return { ok: false, motivo: "demasiado-grande" };
136
- const mime = mimeDeArtefacto(nombre);
137
- return { ok: true, nombre, datos: await readFile(real), ...(mime === undefined ? {} : { mime }) };
136
+ // Del último segmento las DOS cosas, porque el `nombre` que llega puede ser una ruta
137
+ // relativa: un `unzip` deja un árbol en la carpeta, y esa ruta es la que el acto anunció.
138
+ // El `filename=` de la cabecera de descarga sale de aquí, y `stitch/pantalla/screen.png`
139
+ // no es un nombre de fichero; y `mimeDeArtefacto` busca el ÚLTIMO punto de lo que se le
140
+ // pase, así que un `v1.2/mockup` sin extensión le daría por extensión `2/mockup`.
141
+ const base = nombreDeArtefacto(nombre);
142
+ const mime = mimeDeArtefacto(base);
143
+ return {
144
+ ok: true,
145
+ nombre: base,
146
+ datos: await readFile(real),
147
+ ...(mime === undefined ? {} : { mime }),
148
+ };
138
149
  }
139
150
  //# sourceMappingURL=artefactosEnDisco.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"artefactosEnDisco.js","sourceRoot":"","sources":["../../../src/agent/grafo/artefactosEnDisco.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artefactosEnDisco.js","sourceRoot":"","sources":["../../../src/agent/grafo/artefactosEnDisco.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { tool } from "@langchain/core/tools";
2
2
  import { z } from "zod";
3
- import { esRutaDeArtefacto, mimeDeArtefacto, nombreDeArtefacto } from "../../core/artefactos.js";
3
+ import { esRutaDeArtefacto, mimeDeArtefacto, nombreDeArtefacto, rutaRelativaDeArtefacto, } from "../../core/artefactos.js";
4
4
  import { juzgarPantalla, TOPE_DE_PETICIONES, } from "../dispositivos/juezVisual.js";
5
5
  /**
6
6
  * `xone_critica_visual`: enseñarle una captura al crítico y traer su veredicto.
@@ -35,39 +35,135 @@ const Entrada = z.object({
35
35
  pantalla: z
36
36
  .string()
37
37
  .describe("De qué pantalla o colección es la captura, para que el crítico sepa qué mira"),
38
+ /**
39
+ * La MAQUETA, y su ausencia significa algo — por eso lo dice el `describe`.
40
+ *
41
+ * Sin ella el crítico solo busca ROTURAS y no dice nada de si la pantalla se parece a lo
42
+ * que se pidió. Medido: dio verde sobre una calculadora cuyas teclas eran rectángulos donde
43
+ * la maqueta tenía píldoras, y el verde se leyó como «está bien». Un parámetro que no dice
44
+ * qué pasa cuando falta deja ese malentendido intacto.
45
+ */
46
+ referencia: z
47
+ .string()
48
+ .optional()
49
+ .describe("OPCIONAL. La ruta virtual bajo /artefactos/ de la MAQUETA o diseño de referencia. " +
50
+ "Pásala SIEMPRE que el encargo traiga un diseño: con ella comparo la captura contra " +
51
+ "la maqueta (forma, tamaños, colocación). SIN ella solo busco roturas y NO digo nada " +
52
+ "sobre si se parece al diseño."),
38
53
  });
54
+ /**
55
+ * Qué es esta ruta, o el motivo en palabras. **Comprueba y NO lee.**
56
+ *
57
+ * Separada de la lectura a propósito: con dos imágenes en juego, el orden importa. Si se
58
+ * comprobara y abriera cada una por turno, una referencia con la ruta mal escrita se
59
+ * descubriría DESPUÉS de haber leído la captura — y lo que la guarda promete es que no se
60
+ * abre nada hasta que todo lo que se va a abrir está comprobado. Es lo mismo que exige el
61
+ * test de siempre («no se lee NADA antes de comprobarlo»), solo que ahora hay dos.
62
+ *
63
+ * `que` cambia únicamente cómo se nombra en el mensaje: la comprobación es la MISMA para las
64
+ * dos, porque las dos acaban dentro de un modelo.
65
+ */
66
+ function veredictoDeImagen(ruta, que) {
67
+ // Lista BLANCA de forma, sobre el TEXTO que escribe el modelo. `esRutaDeArtefacto` no es
68
+ // un `startsWith`: comprueba la forma entera, segmento a segmento.
69
+ if (!esRutaDeArtefacto(ruta)) {
70
+ return `«${ruta}» no es ${que} de esta sesión. Solo puedo mirar lo que hay bajo /artefactos/.`;
71
+ }
72
+ const nombre = nombreDeArtefacto(ruta);
73
+ const mime = mimeDeArtefacto(nombre);
74
+ if (mime === undefined || !mime.startsWith("image/")) {
75
+ return `«${nombre}» no es una imagen, así que no hay nada que mirar.`;
76
+ }
77
+ return { mime };
78
+ }
79
+ /**
80
+ * Los bytes, ya con la ruta comprobada. Nunca lanza: que falte una imagen no puede llevarse
81
+ * el turno por delante.
82
+ *
83
+ * **Se abre por la ruta RELATIVA y se NOMBRA por el último segmento**, que son dos preguntas
84
+ * distintas y confundirlas era un botón muerto: un `unzip` deja un ÁRBOL, y una maqueta
85
+ * descomprimida vive en `/artefactos/diseno/screen.png`. Con el basename, el `join` de quien
86
+ * lee apuntaba a `<carpeta>/screen.png` y contestaba «no pude abrir» sobre un fichero que
87
+ * estaba ahí y que la propia foto había anunciado.
88
+ */
89
+ async function leer(ruta, mime, deps) {
90
+ try {
91
+ const bytes = await deps.leerArtefacto(rutaRelativaDeArtefacto(ruta));
92
+ return { base64: bytes.toString("base64"), mime };
93
+ }
94
+ catch (error) {
95
+ // El mensaje de un error de Node lleva la ruta absoluta y esto va al modelo: solo el
96
+ // NOMBRE del error.
97
+ return `No pude abrir «${nombreDeArtefacto(ruta)}» (${error instanceof Error ? error.name : "error"}).`;
98
+ }
99
+ }
100
+ /**
101
+ * **Fail-closed: una referencia que se pidió y no se pudo usar es un NO.**
102
+ *
103
+ * Lo tentador es juzgar sin ella y avisar. Es justo el fallo que este parámetro existe para
104
+ * cerrar: un veredicto sin maqueta vuelve como «verde» y se lee como «se parece al diseño».
105
+ * Así que no se juzga nada, y el paso siguiente que se escribe es ARREGLAR LA RUTA — nunca
106
+ * «o llámame sin ella», que sería dejar el modo ciego escrito dentro de la propia tool como
107
+ * alternativa legítima.
108
+ */
109
+ function sinJuzgar(motivo) {
110
+ return [
111
+ motivo,
112
+ "NO he juzgado la captura: comparar contra media maqueta no es comparar, y mi veredicto",
113
+ "se leería como que la pantalla se parece al diseño. Comprueba la ruta de la maqueta",
114
+ "—tiene que ser la que anunció su evento `artefacto`, con sus subcarpetas— y vuelve a",
115
+ "llamarme.",
116
+ ].join("\n");
117
+ }
39
118
  export function crearCriticaVisual(deps) {
40
119
  return tool(async (entrada) => {
41
- // Lista BLANCA de forma, sobre el TEXTO que escribe el modelo. `esRutaDeArtefacto` no es
42
- // un `startsWith`: comprueba la forma entera.
43
- if (!esRutaDeArtefacto(entrada.captura)) {
44
- return `«${entrada.captura}» no es una captura de esta sesión. Solo puedo mirar lo que hay bajo /artefactos/.`;
45
- }
46
- const nombre = nombreDeArtefacto(entrada.captura);
47
- const mime = mimeDeArtefacto(nombre);
48
- if (mime === undefined || !mime.startsWith("image/")) {
49
- return `«${nombre}» no es una imagen, así que no hay nada que mirar.`;
50
- }
51
- let bytes;
52
- try {
53
- bytes = await deps.leerArtefacto(nombre);
54
- }
55
- catch (error) {
56
- // El mensaje de un error de Node lleva la ruta absoluta y esto va al modelo: solo el
57
- // nombre del error. Y se DEVUELVE, no se lanza: que falte una captura no puede
58
- // llevarse el turno por delante.
59
- return `No pude abrir «${nombre}» (${error instanceof Error ? error.name : "error"}).`;
120
+ // Se comprueban las DOS antes de abrir ninguna: ver `veredictoDeImagen`.
121
+ const deLaCaptura = veredictoDeImagen(entrada.captura, "una captura");
122
+ if (typeof deLaCaptura === "string")
123
+ return deLaCaptura;
124
+ const deLaReferencia = entrada.referencia === undefined
125
+ ? undefined
126
+ : veredictoDeImagen(entrada.referencia, "la referencia");
127
+ if (typeof deLaReferencia === "string")
128
+ return sinJuzgar(deLaReferencia);
129
+ const abierta = await leer(entrada.captura, deLaCaptura.mime, deps);
130
+ if (typeof abierta === "string")
131
+ return abierta;
132
+ let referencia;
133
+ if (entrada.referencia !== undefined && deLaReferencia !== undefined) {
134
+ const abiertaRef = await leer(entrada.referencia, deLaReferencia.mime, deps);
135
+ /**
136
+ * **Fail-closed: una referencia que se pidió y no se pudo abrir es un NO.**
137
+ *
138
+ * Lo tentador es juzgar sin ella y avisar. Es justo el fallo que este parámetro
139
+ * existe para cerrar: un veredicto sin maqueta vuelve como «verde» y se lee como
140
+ * «se parece al diseño». Así que no se juzga nada, y el paso siguiente que se
141
+ * escribe es ARREGLAR LA RUTA — nunca «o llámame sin referencia», que sería dejar
142
+ * el modo ciego escrito dentro de la propia tool como alternativa legítima.
143
+ */
144
+ if (typeof abiertaRef === "string")
145
+ return sinJuzgar(abiertaRef);
146
+ referencia = abiertaRef;
60
147
  }
61
148
  let veredicto;
62
149
  try {
63
- veredicto = await juzgarPantalla({ base64: bytes.toString("base64"), mime }, { pantalla: entrada.pantalla }, deps.invocar);
150
+ veredicto = await juzgarPantalla(abierta, { pantalla: entrada.pantalla }, deps.invocar, referencia);
64
151
  }
65
152
  catch (error) {
66
153
  // Fallo del ENTORNO —sin modelo, sin clave, sin red—: se dice, y no se convierte en un
67
154
  // veredicto. Un rojo inventado culparía al trabajo de un problema de la máquina.
68
155
  return `No se pudo consultar al crítico visual: ${error instanceof Error ? error.message : "error"}`;
69
156
  }
70
- const lineas = [`Veredicto visual de «${entrada.pantalla}»: ${veredicto.veredicto}.`];
157
+ /**
158
+ * **La cabecera dice si se COMPARÓ, y no repite la ruta de la referencia.** Lo primero
159
+ * porque «verde» contesta dos preguntas distintas según el modo y quien lo lea tiene
160
+ * que saber cuál le han contestado. Lo segundo porque el argumento lo acaba de escribir
161
+ * quien llama: devolvérselo entero es el eco que ya paga el campo `pantalla`.
162
+ */
163
+ const comparado = referencia === undefined ? "" : " (comparado con la referencia)";
164
+ const lineas = [
165
+ `Veredicto visual de «${entrada.pantalla}»${comparado}: ${veredicto.veredicto}.`,
166
+ ];
71
167
  if (veredicto.observaciones.length > 0) {
72
168
  /**
73
169
  * **El aviso no es cortesía: está medido.** Seis vueltas sobre la misma captura
@@ -111,7 +207,18 @@ export function crearCriticaVisual(deps) {
111
207
  * puede, porque llegar a una pantalla necesita abrir cajones y mirar el árbol.
112
208
  */
113
209
  if (veredicto.veredicto === "rojo") {
114
- lineas.push("Esto es un defecto del proyecto SIN ARREGLAR, y qué hacer con él depende de TU", "encargo — que lo sabes tú y no yo:", "- Si el encargo incluye ARREGLAR o dejar la app bien: encárgaselo a `developer-xone`", " pasándole estas observaciones tal cual, y cuando lo haya corregido manda otra vez", " al conductor a esta misma pantalla y vuelve a llamarme con la captura nueva. No", " des la pantalla por buena hasta que yo la vea en verde.", "- Si el encargo es OTRO —documentar, medir, inventariar—: NO toques el proyecto por", " esto. Anótalo donde estés contando lo que ves y sigue con lo tuyo.", "Lo que NO vale en ninguno de los dos casos es callártelo.");
210
+ /**
211
+ * **Con referencia, el arreglo es de `designer-xone`.** No es un cambio de criterio:
212
+ * una diferencia contra una maqueta es visual por definición —forma, tamaño,
213
+ * colocación, color— y eso es literalmente lo que su `.md` reclama («del color o del
214
+ * tamaño equivocado… también cuando se arregle en un `.xne`»). Sin referencia el
215
+ * hallazgo puede ser cualquier cosa, así que esa rama se queda como estaba.
216
+ */
217
+ const aQuien = referencia === undefined ? "developer-xone" : "designer-xone";
218
+ lineas.push("Esto es un defecto del proyecto SIN ARREGLAR, y qué hacer con él depende de TU", "encargo — que lo sabes tú y no yo:", `- Si el encargo incluye ARREGLAR o dejar la app bien: encárgaselo a \`${aQuien}\``, " pasándole estas observaciones tal cual, y cuando lo haya corregido manda otra vez", " al conductor a esta misma pantalla y vuelve a llamarme con la captura nueva.", referencia === undefined
219
+ ? " No des la pantalla por buena hasta que yo la vea en verde."
220
+ : " Y vuelve CON LA MISMA `referencia`: sin ella mi verde solo diría que nada" +
221
+ "\n está roto, no que la pantalla se parezca al diseño.", "- Si el encargo es OTRO —documentar, medir, inventariar—: NO toques el proyecto por", " esto. Anótalo donde estés contando lo que ves y sigue con lo tuyo.", "Lo que NO vale en ninguno de los dos casos es callártelo.");
115
222
  }
116
223
  if (veredicto.necesito.length > 0) {
117
224
  lineas.push(`Necesita ver ${veredicto.necesito.length === 1 ? "otra pantalla" : "otras pantallas"} para dictaminar: ${veredicto.necesito.join(", ")}.`, `Encarga al conductor que llegue ahí y capture, y vuelve a llamarme con esa captura (como mucho ${TOPE_DE_PETICIONES} veces).`);
@@ -121,7 +228,10 @@ export function crearCriticaVisual(deps) {
121
228
  name: "xone_critica_visual",
122
229
  description: "Enseña una captura de pantalla del aparato a un revisor visual y devuelve qué se ve " +
123
230
  "mal: texto cortado, controles solapados o fuera de la pantalla, cosas ilegibles. " +
124
- "Ve lo que ninguna comprobación estática puede ver. Puede pedir otras pantallas.",
231
+ "Ve lo que ninguna comprobación estática puede ver. Puede pedir otras pantallas. " +
232
+ "Si el encargo trae un DISEÑO o una MAQUETA, pásala en `referencia` y además comparo " +
233
+ "la pantalla contra ella (forma, tamaños, colocación, color). SIN `referencia` mi " +
234
+ "verde significa «nada roto», NO «se parece al diseño».",
125
235
  schema: Entrada,
126
236
  });
127
237
  }
@@ -1 +1 @@
1
- {"version":3,"file":"criticaVisual.js","sourceRoot":"","sources":["../../../src/agent/grafo/criticaVisual.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"criticaVisual.js","sourceRoot":"","sources":["../../../src/agent/grafo/criticaVisual.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,9 @@ import { join } from "node:path";
3
3
  import { CompositeBackend, FilesystemBackend, LocalShellBackend } from "deepagents";
4
4
  import { RUTA_MEMORIA_INTERNA, RUTA_MEMORIA_VIRTUAL } from "./memoriaDeProyecto.js";
5
5
  import { RAIZ_SKILLS, skillsConRuta, skillsMontables } from "./skills.js";
6
- import { artefactoFueraDeSitio, mimeDeArtefacto, nombreDeArtefacto, RUTA_ARTEFACTOS, } from "../../core/artefactos.js";
6
+ import { artefactoFueraDeSitio, esBasuraDeArtefacto, esRutaDeArtefacto, mimeDeArtefacto, nombreDeArtefacto, RUTA_ARTEFACTOS, } from "../../core/artefactos.js";
7
7
  import { RUTA_ADJUNTOS } from "../../core/adjuntos.js";
8
+ import { carpetaDeHotswap, RUTA_HOTSWAP } from "../../core/hotswap.js";
8
9
  import { CARPETA_DE_PLANES, RUTA_PLANES } from "../../core/planes.js";
9
10
  import { entornoDeShell, variablesDeAndroid } from "../../core/shellDeAgente.js";
10
11
  import { localizadorDeAndroid } from "../dispositivos/dispositivosEnMaquina.js";
@@ -95,7 +96,10 @@ enSdk = localizadorDeAndroid({
95
96
  // Sin esto, `xone-arrancar-android` tendría que adivinar dónde está el SDK: en un Mac con
96
97
  // Homebrew `emulator` no está en el PATH y `ANDROID_HOME` suele estar vacía.
97
98
  android: variablesDeAndroid(enSdk),
98
- ...(artefactos === undefined ? {} : { artefactos }),
99
+ // Las DOS carpetas, y la segunda se DERIVA de la primera: es la misma decisión —¿hay una
100
+ // sesión con identidad?— y un segundo parámetro sería un segundo sitio donde contestarla,
101
+ // que es justo el que se cae en un cableado largo.
102
+ ...(artefactos === undefined ? {} : { artefactos, hotswap: carpetaDeHotswap(artefactos) }),
99
103
  });
100
104
  }
101
105
  /**
@@ -280,6 +284,36 @@ export function backendConDescargas(backend, carpetaDeArtefactos) {
280
284
  }
281
285
  return new CompositeBackend(backend, montajes);
282
286
  }
287
+ /**
288
+ * Cuelga `/hotswap/` al lado de los artefactos de la sesión — o sea, FUERA del proyecto.
289
+ *
290
+ * Es donde el script `xone-hotswap` deja lo que saca del contexto: el árbol de controles de
291
+ * una pantalla, el log del aparato. El porqué entero —y la medida de los 10 volcados de los
292
+ * que 6 no volvió a abrir nadie— está en `core/hotswap.ts`.
293
+ *
294
+ * La misma pieza que `/skills/`, `/artefactos/`, `/adjuntos/` y las dos de descarga: otra
295
+ * raíz del `CompositeBackend`, con la barra final obligatoria y sin crear la carpeta al
296
+ * montar. Y **no se anuncia**, igual que las descargas y al contrario que los artefactos:
297
+ * ésa es exactamente la diferencia que esta carpeta existe para marcar.
298
+ *
299
+ * Que esté MONTADA es la otra mitad del arreglo, y no es opcional. El agente tenía la
300
+ * carpeta del disco en `$XONECODE_ARTEFACTOS` y ninguna ruta virtual con la que volver a
301
+ * abrir lo que el script le había guardado; en la traza se le ve probando tres rutas —la
302
+ * absoluta de la máquina incluida— antes de rendirse y leerlo por la shell. Con esto,
303
+ * `read_file("/hotswap/<nombre>")` funciona y el script puede NOMBRAR esa ruta en su salida.
304
+ *
305
+ * No necesita fila en `permisosDe`: los `deny` de ahí son de `write`, así que leer ya está
306
+ * permitido, y escribir cae bajo el `deny` general — que es lo correcto, porque quien escribe
307
+ * aquí es una shell, y una shell no pasa por los permisos.
308
+ */
309
+ export function backendConHotswap(backend, carpetaDeArtefactos) {
310
+ return new CompositeBackend(backend, {
311
+ [RUTA_HOTSWAP]: new FilesystemBackend({
312
+ rootDir: carpetaDeHotswap(carpetaDeArtefactos),
313
+ virtualMode: true,
314
+ }),
315
+ });
316
+ }
283
317
  /**
284
318
  * Envuelve el backend del PROYECTO para que una descarga del agente no pueda aterrizar
285
319
  * dentro.
@@ -368,7 +402,12 @@ export function backendDeAgente(opciones) {
368
402
  const conDescargas = opciones.artefactos === undefined
369
403
  ? conArtefactos
370
404
  : backendConDescargas(conArtefactos, opciones.artefactos.carpeta);
371
- const conAdjuntos = opciones.adjuntos === undefined ? conDescargas : backendConAdjuntos(conDescargas, opciones.adjuntos);
405
+ // Y `/hotswap/`, de la MISMA carpeta de sesión y por el mismo motivo que las descargas: lo
406
+ // que una shell saca del contexto se tiene que poder releer, y no se anuncia.
407
+ const conHotswap = opciones.artefactos === undefined
408
+ ? conDescargas
409
+ : backendConHotswap(conDescargas, opciones.artefactos.carpeta);
410
+ const conAdjuntos = opciones.adjuntos === undefined ? conHotswap : backendConAdjuntos(conHotswap, opciones.adjuntos);
372
411
  /**
373
412
  * Los PLANES van SIEMPRE, sin depender de la sesión: cuelgan del repo local
374
413
  * (`.xonecode/planes/`) y no de la carpeta de una sesión, porque un plan se escribe un día y
@@ -392,9 +431,24 @@ export function backendDeAgente(opciones) {
392
431
  * que decir «he dejado una captura» sin que nada lo respalde — que es la clase de afirmación
393
432
  * que este harness no quiere.
394
433
  *
395
- * **Se compara una FOTO de antes con una de después** (nombre → tamaño + mtime) en vez de
396
- * fiarse de lo que el comando diga que hizo. Un comando puede escribir tres ficheros, o
397
- * ninguno, o pisar el de antes; lo único que lo sabe es la carpeta.
434
+ * **Se compara una FOTO de antes con una de después** (ruta relativa → tamaño + mtime) en
435
+ * vez de fiarse de lo que el comando diga que hizo. Un comando puede escribir tres ficheros,
436
+ * o ninguno, o pisar el de antes; lo único que lo sabe es la carpeta.
437
+ *
438
+ * **Y la foto es RECURSIVA, porque un `unzip` deja un ÁRBOL y no un fichero.** Era
439
+ * `readdirSync` a secas más un `isFile()`, o sea que una carpeta se saltaba entera y con
440
+ * ella todo lo que llevara dentro. Medido sobre una sesión real: el zip de una maqueta se
441
+ * descomprimió en `/artefactos/stitch-restaurantes/stitch_screen_complex/` y salieron CERO
442
+ * anuncios — los tres ficheros estaban en el disco y no existían para nadie, sin pestaña
443
+ * Artefactos (solo existe si hay alguno) y sin tarjeta en el hilo. Es el mismo fallo que la
444
+ * carpeta sin crear, un escalón más adentro. No se baja por un ENLACE: `Dirent.isDirectory()`
445
+ * es falso para uno, y lo que cuelgue de él no es lo que esta sesión produjo.
446
+ *
447
+ * Lo que la recursión hace aparecer también se filtra, y las dos cribas son la misma idea:
448
+ * **no anunciar lo que acaba en una tarjeta muerta**. `esRutaDeArtefacto` es la barrera que
449
+ * el LECTOR aplica, así que un nombre que él rechazaría (`Diseño final.png`) pintaría una
450
+ * fila cuyo único final es un 403; y `esBasuraDeArtefacto` retira el `__MACOSX/._x.png` que
451
+ * un zip de Finder arrastra, que tiene la extensión del bueno y doscientos bytes dentro.
398
452
  *
399
453
  * **Y la carpeta se CREA aquí, antes de correr el comando.** Es la excepción a «no se crea
400
454
  * al montar», y la pagó una captura: `$XONECODE_ARTEFACTOS` la crea `FilesystemBackend.write`
@@ -410,23 +464,38 @@ export function backendDeAgente(opciones) {
410
464
  export function anunciarArtefactosDeLaShell(backend, carpeta, alEscribir) {
411
465
  const foto = () => {
412
466
  const m = new Map();
413
- let nombres;
414
- try {
415
- nombres = readdirSync(carpeta);
416
- }
417
- catch {
418
- return m;
419
- }
420
- for (const nombre of nombres) {
467
+ const recorrer = (dir, prefijo) => {
468
+ let entradas;
421
469
  try {
422
- const e = statSync(join(carpeta, nombre));
423
- if (e.isFile())
424
- m.set(nombre, `${e.size}:${e.mtimeMs}`);
470
+ entradas = readdirSync(dir, { withFileTypes: true });
425
471
  }
426
472
  catch {
427
- // Un fichero que desaparece entre el listado y la medida no es un artefacto nuevo.
473
+ return;
428
474
  }
429
- }
475
+ for (const entrada of entradas) {
476
+ // La clave se compone con `/` y nunca con `sep`: es la ruta VIRTUAL que se va a
477
+ // anunciar, y una barra invertida de Windows no pasaría `esRutaDeArtefacto`.
478
+ const relativa = prefijo + entrada.name;
479
+ // `isDirectory()` sobre un `Dirent` es FALSO para un enlace —el dirent dice
480
+ // `isSymbolicLink()`—, así que no se baja por él. Es la misma disciplina que el
481
+ // árbol de Ficheros, y aquí importa igual: lo que cuelga de un enlace no es lo que
482
+ // esta sesión produjo, y se anunciaría con ruta de artefacto.
483
+ if (entrada.isDirectory()) {
484
+ recorrer(join(dir, entrada.name), relativa + "/");
485
+ continue;
486
+ }
487
+ if (!entrada.isFile())
488
+ continue;
489
+ try {
490
+ const e = statSync(join(dir, entrada.name));
491
+ m.set(relativa, `${e.size}:${e.mtimeMs}`);
492
+ }
493
+ catch {
494
+ // Un fichero que desaparece entre el listado y la medida no es un artefacto nuevo.
495
+ }
496
+ }
497
+ };
498
+ recorrer(carpeta, "");
430
499
  return m;
431
500
  };
432
501
  return new Proxy(backend, {
@@ -455,10 +524,20 @@ export function anunciarArtefactosDeLaShell(backend, carpeta, alEscribir) {
455
524
  // En el `finally`: un comando que acaba en error puede haber dejado el fichero, y
456
525
  // un fallo al listar la carpeta no puede llevarse por delante el resultado.
457
526
  try {
458
- for (const [nombre, marca] of foto()) {
459
- if (antes.get(nombre) === marca)
527
+ for (const [relativa, marca] of foto()) {
528
+ if (antes.get(relativa) === marca)
529
+ continue;
530
+ const ruta = RUTA_ARTEFACTOS + relativa;
531
+ // Lo que el LECTOR rechazaría no se anuncia: su barrera es la lista blanca de
532
+ // forma de `esRutaDeArtefacto`, y una shell escribe el nombre que quiera. Una
533
+ // tarjeta cuyo único final posible es un 403 es la misma mentira que un modal
534
+ // que solo puede acabar en rechazo.
535
+ if (!esRutaDeArtefacto(ruta) || esBasuraDeArtefacto(relativa))
460
536
  continue;
461
- const ruta = RUTA_ARTEFACTOS + nombre;
537
+ // Se compone igual que el Proxy de `write`/`edit`: la RUTA lleva el camino
538
+ // relativo —es lo que después se le pide al lector— y el `nombre` es el
539
+ // último segmento, que es lo que se le enseña a una persona.
540
+ const nombre = nombreDeArtefacto(ruta);
462
541
  const mime = mimeDeArtefacto(nombre);
463
542
  const bytes = Number(marca.split(":")[0] ?? 0);
464
543
  alEscribir({ ruta, nombre, bytes, ...(mime === undefined ? {} : { mime }) });