xonecode 0.29.0 → 0.33.0

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 (82) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-ChyJtiiz.js → EditorDeFichero-H_kGeZt2.js} +1 -1
  2. package/apps/web/dist/assets/{index-IptObD2z.css → index-CWBaotdZ.css} +1 -1
  3. package/apps/web/dist/assets/index-DnGuOvbb.js +499 -0
  4. package/apps/web/dist/assets/{php-B91ZcS-9.js → php-CMFQiZgk.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-DrxrTSz0.js → ruby-Dj3BOBHc.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/config/modelosDeMotor.js +4 -2
  8. package/dist/agent/config/modelosDeMotor.js.map +1 -1
  9. package/dist/agent/config/settingsEnDisco.js +26 -0
  10. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  11. package/dist/agent/dispositivos/cajasDeMaqueta.js +130 -0
  12. package/dist/agent/dispositivos/cajasDeMaqueta.js.map +1 -0
  13. package/dist/agent/dispositivos/geometriaEnDisco.js +25 -0
  14. package/dist/agent/dispositivos/geometriaEnDisco.js.map +1 -0
  15. package/dist/agent/dispositivos/juezVisual.js +84 -3
  16. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  17. package/dist/agent/grafo/criticaVisual.js +240 -11
  18. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  19. package/dist/agent/grafo/lectorDeReferencias.js +21 -0
  20. package/dist/agent/grafo/lectorDeReferencias.js.map +1 -1
  21. package/dist/agent/grafo/validarFicheroXone.js +114 -0
  22. package/dist/agent/grafo/validarFicheroXone.js.map +1 -0
  23. package/dist/agent/motores/motoresLocales.js +429 -0
  24. package/dist/agent/motores/motoresLocales.js.map +1 -0
  25. package/dist/agent/motores/trueforge/sesionTrueforge.js +79 -11
  26. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  27. package/dist/agent/motores/trueforge/toolsDeFichero.js +24 -6
  28. package/dist/agent/motores/trueforge/toolsDeFichero.js.map +1 -1
  29. package/dist/agent/sesiones/adjuntosDeSesion.js +125 -5
  30. package/dist/agent/sesiones/adjuntosDeSesion.js.map +1 -1
  31. package/dist/agent/subagentes/ejecutableEnWindows.js +139 -0
  32. package/dist/agent/subagentes/ejecutableEnWindows.js.map +1 -0
  33. package/dist/agent/subagentes/subagenteCodex.js +7 -2
  34. package/dist/agent/subagentes/subagenteCodex.js.map +1 -1
  35. package/dist/agent/subagentes/subagenteOpencode.js +12 -5
  36. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
  37. package/dist/agent/turno/resumenDeTool.js +2 -0
  38. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  39. package/dist/core/adjuntos.js +10 -2
  40. package/dist/core/adjuntos.js.map +1 -1
  41. package/dist/core/contextos.js +20 -0
  42. package/dist/core/contextos.js.map +1 -1
  43. package/dist/core/cssBienFormado.js +97 -0
  44. package/dist/core/cssBienFormado.js.map +1 -0
  45. package/dist/core/descripcionDePantalla.js +354 -0
  46. package/dist/core/descripcionDePantalla.js.map +1 -0
  47. package/dist/core/estiloMedido.js +332 -0
  48. package/dist/core/estiloMedido.js.map +1 -0
  49. package/dist/core/geometriaDePantalla.js +437 -0
  50. package/dist/core/geometriaDePantalla.js.map +1 -0
  51. package/dist/core/jsParaRhino.js +93 -0
  52. package/dist/core/jsParaRhino.js.map +1 -0
  53. package/dist/core/motoresLocales.js +128 -0
  54. package/dist/core/motoresLocales.js.map +1 -0
  55. package/dist/core/notify.js +2 -0
  56. package/dist/core/notify.js.map +1 -1
  57. package/dist/core/nucleoXone.js +6 -6
  58. package/dist/core/nucleoXone.js.map +1 -1
  59. package/dist/core/settings.js +5 -0
  60. package/dist/core/settings.js.map +1 -1
  61. package/dist/core/shellDeAgente.js +13 -0
  62. package/dist/core/shellDeAgente.js.map +1 -1
  63. package/dist/core/xmlBienFormado.js +206 -0
  64. package/dist/core/xmlBienFormado.js.map +1 -0
  65. package/dist/core/zipDeAdjunto.js +79 -0
  66. package/dist/core/zipDeAdjunto.js.map +1 -0
  67. package/dist/web/servidor/arranque.js +84 -0
  68. package/dist/web/servidor/arranque.js.map +1 -1
  69. package/dist/web/servidor/transporte.js.map +1 -1
  70. package/package.json +2 -1
  71. package/skills/xone-development/SKILL.md +2 -1
  72. package/skills/xone-development/references/css/buenas-practicas-y-parser.md +2 -1
  73. package/skills/xone-development/references/css/de-una-maqueta-a-xone.md +81 -0
  74. package/skills/xone-development/references/css/patrones-material-y-temas.md +15 -13
  75. package/skills/xone-development/references/css/propiedades-y-herencia.md +2 -2
  76. package/skills/xone-development/references/css/selectores-unidades-colores.md +8 -3
  77. package/skills/xone-development/references/indice-completo.md +1 -0
  78. package/skills/xone-hotswap/lib/geometria.mjs +147 -0
  79. package/skills/xone-hotswap/scripts/xone-captura-android +6 -0
  80. package/skills/xone-hotswap/scripts/xone-hotswap +8 -0
  81. package/skills/xone-hotswap/scripts/xone-recargar-android +15 -0
  82. package/apps/web/dist/assets/index-CFIrUOmW.js +0 -499
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Un `.zip` que la persona adjunta en el chat se DESCOMPRIME al guardarlo: QUÉ se extrae y dónde.
3
+ *
4
+ * Existe por una conversación de MyAllXOne: la persona adjuntó el diseño de Stitch en un `.zip`
5
+ * (`screen.png`, `code.html`, `DESIGN.md`) y ningún agente tenía con qué abrirlo — `read_file` y
6
+ * `describe_image` no entran en un zip—. Siete minutos y 2,6 millones de tokens de entrada después,
7
+ * con herramientas inventadas (`unzip`, `extraer_zip`, `bash`…), el turno acabó pidiendo los ficheros
8
+ * sueltos. Lo hace el HARNESS, no el agente: el modelo no tiene que saber nada de zips, ve una carpeta.
9
+ *
10
+ * **Este módulo es PURO** (la regla); quien descomprime y escribe es
11
+ * `agent/sesiones/adjuntosDeSesion.ts`. Las barreras son las de instalar una skill desde un `.zip`
12
+ * (`core/zipDeSkill.ts`), porque es la misma entrada de poca confianza:
13
+ * - **forma de ruta en lista BLANCA** (`rutaDeZipAceptable`: el «zip slip» no se para con
14
+ * `includes("..")`), y además cada segmento tiene que ser un nombre de adjunto válido
15
+ * (`nombreDeAdjuntoAceptable`): es lo que luego lee el agente bajo `/adjuntos/`;
16
+ * - **topes contra un zip BOMBA**: cuántas entradas y cuánto ocupa todo descomprimido, decididos con
17
+ * el tamaño que DECLARA cada entrada, ANTES de inflar nada;
18
+ * - **una mala se lleva el zip entero**: se decide todo antes de escribir.
19
+ * Lo que un empaquetador arrastra sin que nadie lo pida (`__MACOSX/`, `.DS_Store`) se descarta, y una
20
+ * única carpeta envolvente se quita, como en las skills.
21
+ */
22
+ import { nombreDeAdjuntoAceptable } from "./adjuntos.js";
23
+ import { carpetaComun, rutaDeZipAceptable, TOPE_DE_ENTRADAS, TOPE_DESCOMPRIMIDO } from "./zipDeSkill.js";
24
+ export { TOPE_DE_ENTRADAS as TOPE_DE_ENTRADAS_DE_ADJUNTO, TOPE_DESCOMPRIMIDO as TOPE_DESCOMPRIMIDO_DE_ADJUNTO };
25
+ /** ¿Es un `.zip` por su nombre? Lo que se descomprime lo decide la extensión, como el resto de adjuntos. */
26
+ export function esZip(nombre) {
27
+ return /\.zip$/i.test(nombre);
28
+ }
29
+ /** La carpeta donde se extrae `x.zip`: `x`, al lado del zip. */
30
+ export function carpetaDeExtraccion(nombreDelZip) {
31
+ return nombreDelZip.replace(/\.zip$/i, "");
32
+ }
33
+ /** Lo que arrastra un empaquetador y nadie pidió. */
34
+ function esBasura(ruta) {
35
+ const segmentos = ruta.split("/");
36
+ return segmentos[0] === "__MACOSX" || segmentos.some((s) => s === ".DS_Store" || s.startsWith("._"));
37
+ }
38
+ /**
39
+ * Un segmento como nombre de adjunto: lo que no es letra o cifra ASCII, punto, guion o guion bajo pasa
40
+ * a «_» («Captura de pantalla.png» → «Captura_de_pantalla.png»), en vez de tirar el zip entero por un
41
+ * espacio. Es SEGURO porque va DESPUÉS de `rutaDeZipAceptable`: la ruta ya es de segmentos llanos, sin
42
+ * `..` ni barras; y lo que salga se vuelve a mirar con `nombreDeAdjuntoAceptable`.
43
+ */
44
+ export function segmentoDeAdjunto(segmento) {
45
+ return segmento.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9._-]/g, "_");
46
+ }
47
+ /**
48
+ * Qué se extrae. `entradas` son los FICHEROS del zip (sin las carpetas) con el tamaño que declaran
49
+ * descomprimidos. Devuelve el motivo si no se extrae nada; nunca lanza. `disponible` es lo que aún
50
+ * cabe en los adjuntos de la sesión.
51
+ */
52
+ export function planDeExtraccion(entradas, disponible = TOPE_DESCOMPRIMIDO) {
53
+ const utiles = entradas.filter((e) => !esBasura(e.ruta));
54
+ if (utiles.length === 0)
55
+ return { error: "el .zip está vacío" };
56
+ if (utiles.length > TOPE_DE_ENTRADAS)
57
+ return { error: `el .zip trae más de ${TOPE_DE_ENTRADAS} ficheros` };
58
+ // El nombre de la entrada mala NO se devuelve: lo eligió quien empaquetó el zip.
59
+ if (utiles.some((e) => !rutaDeZipAceptable(e.ruta)))
60
+ return { error: "el .zip trae una ruta que se sale de su carpeta" };
61
+ const total = utiles.reduce((suma, e) => suma + e.bytes, 0);
62
+ if (total > TOPE_DESCOMPRIMIDO)
63
+ return { error: "el .zip ocupa demasiado descomprimido" };
64
+ if (total > disponible)
65
+ return { error: "descomprimido no cabe en los adjuntos de esta sesión" };
66
+ const prefijo = carpetaComun(utiles.map((e) => e.ruta));
67
+ const ficheros = utiles.map((e) => ({
68
+ enElZip: e.ruta,
69
+ destino: (prefijo === undefined ? e.ruta : e.ruta.slice(prefijo.length + 1)).split("/").map(segmentoDeAdjunto).join("/"),
70
+ }));
71
+ if (ficheros.some((f) => !f.destino.split("/").every(nombreDeAdjuntoAceptable))) {
72
+ return { error: "el .zip trae nombres que no valen para un adjunto" };
73
+ }
74
+ if (new Set(ficheros.map((f) => f.destino.toLowerCase())).size !== ficheros.length) {
75
+ return { error: "el .zip trae dos ficheros que quedarían con el mismo nombre" };
76
+ }
77
+ return { ficheros };
78
+ }
79
+ //# sourceMappingURL=zipDeAdjunto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zipDeAdjunto.js","sourceRoot":"","sources":["../../src/core/zipDeAdjunto.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,8 @@ import { recargarFicheroDelProyecto, relanzarEnAparato, } from "../../agent/disp
61
61
  import { textoDeRecarga } from "../../core/recargaEnCaliente.js";
62
62
  import { nombreDeApp } from "../../core/descriptoresDeApp.js";
63
63
  import { modelosDeMotor } from "../../agent/config/modelosDeMotor.js";
64
+ import { crearServicioDeMotoresLocales } from "../../agent/motores/motoresLocales.js";
65
+ import { esMotorLocal } from "../../core/motoresLocales.js";
64
66
  import { parsear, compatibleConOpenAi, esProveedorPersonalizado, idDeProveedorPersonalizado, motivoDeEndpointInaceptable, motivoDeSlugInaceptable, nombreDeProveedor, PROVEEDORES, slugDesdeNombre, resolver, SIN_CREDENCIAL, PAPELES, } from "../../core/modelos.js";
65
67
  import { hayCredencial } from "../../cli/consola.js";
66
68
  import { motivoDeClaveInaceptable } from "../../core/config.js";
@@ -4293,6 +4295,68 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
4293
4295
  modelosPorMotor.set(motor, r);
4294
4296
  emitirlos(r);
4295
4297
  };
4298
+ /**
4299
+ * Ajustes → Motores locales. El servicio vive lo que vive el servidor (un login en curso es un
4300
+ * proceso suyo) y se crea al primer uso: la sección casi nunca se abre.
4301
+ */
4302
+ let servicioDeMotores;
4303
+ const emitirMotores = () => {
4304
+ const motores = servicioDeMotores?.foto();
4305
+ if (motores !== undefined)
4306
+ emitir({ clase: "motoresLocales", motores });
4307
+ };
4308
+ const atenderMotorLocal = async (mensaje) => {
4309
+ if (opciones.motoresLocales === undefined) {
4310
+ // Una lista VACÍA y no un `informar`: ese no llega al navegador desde el vestíbulo, y la
4311
+ // sección se quedaría en «Comprobando…» para siempre. Vacía es «aquí no hay motores que
4312
+ // mirar», y la sección lo dice.
4313
+ emitir({ clase: "motoresLocales", motores: [] });
4314
+ return;
4315
+ }
4316
+ servicioDeMotores ??= opciones.motoresLocales(emitirMotores);
4317
+ const servicio = servicioDeMotores;
4318
+ if (mensaje.accion === "medir") {
4319
+ // Lo que ya se sabe sale al momento; la medida nueva llega detrás.
4320
+ emitirMotores();
4321
+ await servicio.medir();
4322
+ return;
4323
+ }
4324
+ if (!esMotorLocal(mensaje.motor))
4325
+ return;
4326
+ const motor = mensaje.motor;
4327
+ switch (mensaje.accion) {
4328
+ case "probar":
4329
+ await servicio.probar(motor);
4330
+ return;
4331
+ case "login":
4332
+ if (!servicio.iniciarSesion(motor, mensaje.modo === "console" ? "console" : "claudeai")) {
4333
+ informar("ya hay un inicio de sesión en curso, o este motor no se puede iniciar desde aquí");
4334
+ }
4335
+ return;
4336
+ case "codigo":
4337
+ if (typeof mensaje.codigo !== "string" || !servicio.enviarCodigo(motor, mensaje.codigo)) {
4338
+ informar("no hay ningún inicio de sesión esperando un código, o el código no es válido");
4339
+ }
4340
+ return;
4341
+ case "cancelar":
4342
+ servicio.cancelarSesion(motor);
4343
+ return;
4344
+ case "navegador":
4345
+ if (!servicio.abrirNavegador(motor))
4346
+ informar("todavía no hay ninguna página de inicio de sesión que abrir");
4347
+ return;
4348
+ case "consola":
4349
+ if (!servicio.abrirConsola(motor))
4350
+ informar("en este sistema no se puede abrir una consola desde aquí");
4351
+ return;
4352
+ case "modelo":
4353
+ if (mensaje.modelo !== undefined && typeof mensaje.modelo !== "string")
4354
+ return;
4355
+ if (!servicio.elegirModelo(motor, mensaje.modelo))
4356
+ informar("ese modelo no se ha podido guardar: tiene que ser «proveedor/modelo»");
4357
+ return;
4358
+ }
4359
+ };
4296
4360
  /** Un paso del alta resuelto en el navegador. Cada rama termina volviendo a anunciar. */
4297
4361
  const atenderAlta = async (mensaje) => {
4298
4362
  // Se limpia al empezar: un aviso viejo pegado a un paso que ya salió bien mentiría.
@@ -5896,6 +5960,15 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
5896
5960
  respuesta.end();
5897
5961
  return;
5898
5962
  }
5963
+ if (typeof mensaje === "object" &&
5964
+ mensaje !== null &&
5965
+ mensaje.clase === "motorLocal" &&
5966
+ ["medir", "probar", "login", "codigo", "cancelar", "navegador", "consola", "modelo"].includes(mensaje.accion)) {
5967
+ void atenderMotorLocal(mensaje).catch(contar);
5968
+ respuesta.writeHead(204);
5969
+ respuesta.end();
5970
+ return;
5971
+ }
5899
5972
  if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "modelosDeMotor" && typeof mensaje.motor === "string") {
5900
5973
  void atenderModelosDeMotor(mensaje.motor).catch(contar);
5901
5974
  respuesta.writeHead(204);
@@ -6643,6 +6716,14 @@ export function ajusteDeWorkspaceCableado(opciones) {
6643
6716
  export function ficherosCableados() {
6644
6717
  return { leerFichero: leerFicheroDeProyecto, escribirFichero: escribirFicheroDeProyecto, baseDeFichero, iconoDelProyecto };
6645
6718
  }
6719
+ /**
6720
+ * Los motores locales de Ajustes, con el servicio de VERDAD: sin más que el aviso, todo lo demás
6721
+ * —el binario del SDK, los procesos, la prueba por el mismo puerto que un subagente— son sus
6722
+ * omisiones. Compuesto aquí y no en línea para que la composición de producción tenga test.
6723
+ */
6724
+ export function motoresLocalesCableados() {
6725
+ return { motoresLocales: (alCambiar) => crearServicioDeMotoresLocales({ alCambiar }) };
6726
+ }
6646
6727
  /**
6647
6728
  * Los siete puertos de los emuladores, cableados con las funciones de verdad — extraídos por el
6648
6729
  * MISMO motivo que `ajusteDeWorkspaceCableado`: dentro de `arrancarConsolaWeb` serían un literal
@@ -7181,6 +7262,9 @@ export async function arrancarConsolaWeb(opciones) {
7181
7262
  leerArtefactoCrudo,
7182
7263
  correrPasoDeReceta: (receta, paso, alSalirLinea) => correrPasoDeReceta(receta, paso, { alSalirLinea }),
7183
7264
  modelosDeMotor,
7265
+ // Con el servicio REAL, compuesto fuera para que tenga test (`motoresLocalesCableados`):
7266
+ // olvidarlo aquí deja la sección diciendo «esta ejecución no puede» con todo en verde.
7267
+ ...motoresLocalesCableados(),
7184
7268
  // La máquina de verdad: adb/emulator del PATH o del SDK, xcrun solo en macOS y solo con
7185
7269
  // herramientas de desarrollo. Cada proceso con su tope.
7186
7270
  detectarDispositivos: () => detectarDispositivos({}, cargarSettings().settings.dispositivos ?? {}),