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.
- package/apps/web/dist/assets/{index-CtYEobZI.css → index-DKZqgHtg.css} +1 -1
- package/apps/web/dist/assets/{index-mTaG_gr6.js → index-LdUr7EoT.js} +63 -63
- package/apps/web/dist/assets/{php-BEI3vU5S.js → php-Cg-eWGpQ.js} +1 -1
- package/apps/web/dist/assets/{ruby-DCYozE_k.js → ruby-EcV7Hon3.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/dispositivos/juezVisual.js +88 -4
- package/dist/agent/dispositivos/juezVisual.js.map +1 -1
- package/dist/agent/grafo/artefactosEnDisco.js +15 -4
- package/dist/agent/grafo/artefactosEnDisco.js.map +1 -1
- package/dist/agent/grafo/criticaVisual.js +134 -24
- package/dist/agent/grafo/criticaVisual.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +101 -22
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +1 -1
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/sesiones/checkpointer.js +183 -1
- package/dist/agent/sesiones/checkpointer.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +3 -1
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +9 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/podar.js +112 -0
- package/dist/cli/podar.js.map +1 -0
- package/dist/core/actos.js.map +1 -1
- package/dist/core/artefactos.js +47 -0
- package/dist/core/artefactos.js.map +1 -1
- package/dist/core/hotswap.js +71 -0
- package/dist/core/hotswap.js.map +1 -0
- package/dist/core/podaDeCheckpoint.js +74 -0
- package/dist/core/podaDeCheckpoint.js.map +1 -0
- package/dist/core/shellDeAgente.js +12 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/web/servidor/arranque.js +53 -5
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/consolaWeb.js +42 -5
- package/dist/web/servidor/consolaWeb.js.map +1 -1
- package/dist/web/servidor/pielWeb.js +13 -4
- package/dist/web/servidor/pielWeb.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +22 -0
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +1 -1
- package/skills/xone-hotswap/SKILL.md +8 -4
- package/skills/xone-hotswap/scripts/xone-hotswap +42 -7
package/apps/web/dist/index.html
CHANGED
|
@@ -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-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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
|
-
|
|
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":"AAgCA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,qBAAqB,GAAU,SAAS,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AACvC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD,qEAAqE;AACrE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,MAAc;QACxB,KAAK,CAAC,kDAAkD,qBAAqB,OAAO,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;
|
|
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
|
-
|
|
137
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,eAAe,EACf,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,
|
|
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
|
-
//
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,
|
|
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
|
-
|
|
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
|
-
|
|
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** (
|
|
396
|
-
* fiarse de lo que el comando diga que hizo. Un comando puede escribir tres ficheros,
|
|
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
|
-
|
|
414
|
-
|
|
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
|
-
|
|
423
|
-
if (e.isFile())
|
|
424
|
-
m.set(nombre, `${e.size}:${e.mtimeMs}`);
|
|
470
|
+
entradas = readdirSync(dir, { withFileTypes: true });
|
|
425
471
|
}
|
|
426
472
|
catch {
|
|
427
|
-
|
|
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 [
|
|
459
|
-
if (antes.get(
|
|
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
|
-
|
|
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 }) });
|