xonecode 0.20.3 → 0.22.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 (88) hide show
  1. package/apps/web/dist/assets/index-5rtynJV_.js +490 -0
  2. package/apps/web/dist/assets/index-Cq6c6WEF.css +1 -0
  3. package/apps/web/dist/assets/{php-CDg2Quc0.js → php-BTFUgUDo.js} +1 -1
  4. package/apps/web/dist/assets/{ruby-DlFaAWQT.js → ruby-BKKiBtJK.js} +1 -1
  5. package/apps/web/dist/index.html +2 -2
  6. package/dist/agent/conectores/servicioDeConectores.js +86 -45
  7. package/dist/agent/conectores/servicioDeConectores.js.map +1 -1
  8. package/dist/agent/config/modelos.js +4 -0
  9. package/dist/agent/config/modelos.js.map +1 -1
  10. package/dist/agent/config/mudanzaEnDisco.js +237 -3
  11. package/dist/agent/config/mudanzaEnDisco.js.map +1 -1
  12. package/dist/agent/config/selectorEnMaquina.js +3 -2
  13. package/dist/agent/config/selectorEnMaquina.js.map +1 -1
  14. package/dist/agent/config/settingsEnDisco.js +76 -2
  15. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  16. package/dist/agent/dispositivos/arranqueDeEmulador.js +27 -3
  17. package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -1
  18. package/dist/agent/dispositivos/instalacionEnMaquina.js +240 -29
  19. package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -1
  20. package/dist/agent/dispositivos/lanzamientoEnMaquina.js +12 -17
  21. package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -1
  22. package/dist/agent/grafo/proyecto.js +91 -50
  23. package/dist/agent/grafo/proyecto.js.map +1 -1
  24. package/dist/agent/motores/trueforge/capacidades.js +22 -0
  25. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  26. package/dist/agent/motores/trueforge/medidaAutomatica.js +16 -3
  27. package/dist/agent/motores/trueforge/medidaAutomatica.js.map +1 -1
  28. package/dist/agent/motores/trueforge/modeloLangchain.js +7 -1
  29. package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
  30. package/dist/agent/motores/trueforge/sesionTrueforge.js +135 -14
  31. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  32. package/dist/agent/motores/trueforge/toolsDeConectores.js +128 -0
  33. package/dist/agent/motores/trueforge/toolsDeConectores.js.map +1 -0
  34. package/dist/agent/motores/trueforge/traerDeStitch.js +142 -0
  35. package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -0
  36. package/dist/agent/subagentes/consumoExterno.js +15 -8
  37. package/dist/agent/subagentes/consumoExterno.js.map +1 -1
  38. package/dist/agent/subagentes/escrituraExterna.js +69 -2
  39. package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
  40. package/dist/agent/subagentes/subagenteExterno.js +17 -1
  41. package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
  42. package/dist/agent/turno/turnoReal.js +1 -0
  43. package/dist/agent/turno/turnoReal.js.map +1 -1
  44. package/dist/cli/main.js +5 -2
  45. package/dist/cli/main.js.map +1 -1
  46. package/dist/core/actos.js +44 -1
  47. package/dist/core/actos.js.map +1 -1
  48. package/dist/core/agentes.js +13 -2
  49. package/dist/core/agentes.js.map +1 -1
  50. package/dist/core/comandoExterno.js +69 -0
  51. package/dist/core/comandoExterno.js.map +1 -0
  52. package/dist/core/conectores.js +148 -3
  53. package/dist/core/conectores.js.map +1 -1
  54. package/dist/core/copiaDeAvd.js +111 -0
  55. package/dist/core/copiaDeAvd.js.map +1 -0
  56. package/dist/core/mudanzaDeWorkspace.js +105 -15
  57. package/dist/core/mudanzaDeWorkspace.js.map +1 -1
  58. package/dist/core/ports.js +28 -0
  59. package/dist/core/ports.js.map +1 -1
  60. package/dist/core/puertosDeAvd.js +64 -0
  61. package/dist/core/puertosDeAvd.js.map +1 -0
  62. package/dist/core/selectorDeCarpeta.js +88 -9
  63. package/dist/core/selectorDeCarpeta.js.map +1 -1
  64. package/dist/core/settings.js +60 -1
  65. package/dist/core/settings.js.map +1 -1
  66. package/dist/core/shellDeAgente.js +11 -0
  67. package/dist/core/shellDeAgente.js.map +1 -1
  68. package/dist/core/stitch.js +114 -0
  69. package/dist/core/stitch.js.map +1 -0
  70. package/dist/web/servidor/arranque.js +523 -52
  71. package/dist/web/servidor/arranque.js.map +1 -1
  72. package/dist/web/servidor/consolaDeTarea.js +4 -1
  73. package/dist/web/servidor/consolaDeTarea.js.map +1 -1
  74. package/dist/web/servidor/pielWeb.js +3 -1
  75. package/dist/web/servidor/pielWeb.js.map +1 -1
  76. package/dist/web/servidor/transporte.js.map +1 -1
  77. package/dist/web/servidor/vestibulo.js +154 -48
  78. package/dist/web/servidor/vestibulo.js.map +1 -1
  79. package/package.json +1 -1
  80. package/skills/xone-hotswap/SKILL.md +26 -10
  81. package/skills/xone-hotswap/lib/dispositivo.mjs +97 -3
  82. package/skills/xone-hotswap/references/conexion-y-despliegue.md +27 -15
  83. package/skills/xone-hotswap/scripts/xone-arrancar-android +9 -2
  84. package/skills/xone-hotswap/scripts/xone-desplegar-android +12 -6
  85. package/skills/xone-hotswap/scripts/xone-hotswap +32 -10
  86. package/skills/xone-hotswap/scripts/xone-reiniciar-android +23 -7
  87. package/apps/web/dist/assets/index-40sIfNsT.js +0 -485
  88. package/apps/web/dist/assets/index-BYTna8Os.css +0 -1
@@ -53,8 +53,9 @@ import { lanzarEnDispositivo, } from "../../agent/dispositivos/lanzamientoEnMaqu
53
53
  import { motivoDeBloqueo, puedeLanzarse, } from "../../core/puedeLanzarse.js";
54
54
  import { PLATAFORMAS_DE_DISPOSITIVO } from "../../core/settings.js";
55
55
  import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
56
- import { arrancarEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
57
- import { correrPasoDeReceta } from "../../agent/dispositivos/instalacionEnMaquina.js";
56
+ import { arrancarEmulador, pararEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
57
+ import { borrarAvd, clonarAvd, correrPasoDeReceta, crearAvd } from "../../agent/dispositivos/instalacionEnMaquina.js";
58
+ import { asignarPuertosPendientes, motivoDeNombreDeAvdInaceptable, motivoDePuertoInaceptable, puertoDeAvd, } from "../../core/puertosDeAvd.js";
58
59
  import { abrirCarpetaDelSistema, abrirDirectorioDelSistema } from "../../agent/config/selectorEnMaquina.js";
59
60
  import { modelosDeMotor } from "../../agent/config/modelosDeMotor.js";
60
61
  import { parsear, compatibleConOpenAi, esProveedorPersonalizado, idDeProveedorPersonalizado, motivoDeEndpointInaceptable, motivoDeSlugInaceptable, nombreDeProveedor, PROVEEDORES, slugDesdeNombre, resolver, SIN_CREDENCIAL, PAPELES, } from "../../core/modelos.js";
@@ -65,10 +66,10 @@ import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDi
65
66
  import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
66
67
  import { crearCheckpointerDeProyecto, mantenimientoDelCheckpointer, } from "../../agent/sesiones/checkpointer.js";
67
68
  import { resumenDePoda } from "../../core/podaDeCheckpoint.js";
68
- import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
69
+ import { cargarSettings, guardarConcurrenciaDeTareas, guardarDepurar, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarAjusteDeAvd, quitarAjustesDeAvd, guardarPuertosAsignados, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
69
70
  import { dentroDelWorkspace, depuracionActiva, expandirConCasa, mismaRuta, motivoParaNoOlvidarEntorno, motivoDeNombreDeEntornoInaceptable, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
70
71
  import { esModoDeEscritura, MODO_POR_OMISION, } from "../../core/modoDeEscritura.js";
71
- import { mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
72
+ import { megasDeLasMudanzas, mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
72
73
  import { elegirCarpetaEnMaquina, haySelectorDeCarpeta } from "../../agent/config/selectorEnMaquina.js";
73
74
  import { cloudstudioDelProyecto } from "../../agent/config/configEnDisco.js";
74
75
  import { abrirEnSistema, olvidarEntorno as olvidarCredencialesDeEntorno, rutaAuthPorDefecto } from "../../agent/cloudstudio/cloudstudioMcp.js";
@@ -107,7 +108,7 @@ import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo,
107
108
  // doblan.
108
109
  import { filaDeTarea } from "./transporte.js";
109
110
  import { esEsfuerzo, nivelesDeEsfuerzo } from "../../core/esfuerzo.js";
110
- import { CATALOGO_DE_CONECTORES, definicionDelCable, RUTA_CALLBACK_MCP } from "../../core/conectores.js";
111
+ import { CATALOGO_DE_CONECTORES, conectoresParaElAgente, definicionDelCable, RUTA_CALLBACK_MCP } from "../../core/conectores.js";
111
112
  import { servicioDeConectoresCableado } from "../../agent/conectores/servicioDeConectores.js";
112
113
  import { crearGestorJira } from "../../agent/conectores/gestorJira.js";
113
114
  import { crearGestorNotion } from "../../agent/conectores/gestorNotion.js";
@@ -1020,6 +1021,14 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1020
1021
  })),
1021
1022
  };
1022
1023
  };
1024
+ /**
1025
+ * El último progreso de la mudanza del workspace EN MARCHA, o ausente. Existe para la ráfaga:
1026
+ * un navegador que reconecta a mitad de una copia larga tira la mudanza al caerse el cable
1027
+ * (`store.ts#marcarDesconectado`) y el `resultado` solo va a quien está conectado al acabar,
1028
+ * así que sin esto no sabría que sigue una —y los controles del workspace se le quedarían
1029
+ * sueltos mientras el servidor niega cualquier otro cambio—.
1030
+ */
1031
+ let ultimoProgresoDeMudanza;
1023
1032
  /**
1024
1033
  * Engancha el cable a la consola que toque, con el transcript entero por delante.
1025
1034
  *
@@ -1114,6 +1123,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1114
1123
  cliente(tareas);
1115
1124
  if (workspace !== undefined)
1116
1125
  cliente(workspace);
1126
+ if (ultimoProgresoDeMudanza !== undefined)
1127
+ cliente({ clase: "mudanzaDeWorkspace", progreso: ultimoProgresoDeMudanza });
1117
1128
  if (depuracion !== undefined)
1118
1129
  cliente(depuracion);
1119
1130
  if (conectores !== undefined)
@@ -1124,12 +1135,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1124
1135
  modelo: consumoDeLaSesion.modelo,
1125
1136
  externo: consumoDeLaSesion.externo,
1126
1137
  ventana: ventanaDeAhora(consumoDeLaSesion.contexto),
1138
+ ...(consumoDeLaSesion.porModelo === undefined ? {} : { porModelo: consumoDeLaSesion.porModelo }),
1127
1139
  });
1128
1140
  }
1129
1141
  // La foto de la máquina, si ya se tomó. Si no, se dispara abajo UNA vez y llega a
1130
1142
  // todos por el SSE cuando termine: no se espera aquí, que son varios procesos.
1131
1143
  if (informeDeDispositivos !== undefined) {
1132
- cliente({ clase: "dispositivos", informe: informeDeDispositivos, ajustes: ajustesDeDispositivos() });
1144
+ cliente(mensajeDeDispositivos(informeDeDispositivos));
1133
1145
  }
1134
1146
  // Y si hay turno corriendo, se dice: quien conecta a mitad no vio el mensaje que lo
1135
1147
  // anunció, y sin esto vería el compositor encendido y sin borde —«no pasa nada»—
@@ -1274,6 +1286,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1274
1286
  ? { clase: "workspace", ruta }
1275
1287
  : { clase: "workspace", ruta, puedeElegir: true };
1276
1288
  };
1289
+ /** Hay una mudanza del workspace en marcha: una segunda se niega, y crear una tarea también. */
1290
+ let mudandoWorkspace = false;
1277
1291
  const emitirWorkspace = () => {
1278
1292
  const m = mensajeDeWorkspace();
1279
1293
  if (m !== undefined)
@@ -1662,6 +1676,30 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1662
1676
  };
1663
1677
  /** Los ajustes de destino, o `{}` —que es «se miran todos»— si esta ejecución no los lee. */
1664
1678
  const ajustesDeDispositivos = () => opciones.ajustesDeDispositivos?.() ?? {};
1679
+ /**
1680
+ * Qué dispositivo tiene elegido CADA consola abierta, no solo la del foco: con dos proyectos
1681
+ * abiertos sobre dos emuladores, lo que la PASTILLA del chat tiene que poder decir es «este lo usa Tienda».
1682
+ *
1683
+ * Se pregunta a las consolas vivas y no se guarda, igual que `sesionesTrabajando`: es un
1684
+ * estado de este instante. El `proyecto` es el SEGMENTO de su carpeta —el mismo nombre que ya
1685
+ * viaja en el lanzamiento (`medirLanzable`) y que enseña la barra—, nunca la ruta: `sinRutas`.
1686
+ * **Límite declarado**: va por `basename(raiz)`, así que el mismo nombre de proyecto en dos
1687
+ * entornos (`<entorno>/<proyecto>`) se confunde.
1688
+ */
1689
+ const dispositivosEnUso = () => vestibulo
1690
+ .proyectosAbiertos()
1691
+ .flatMap((c) => (c.dispositivo === undefined ? [] : [{ id: c.dispositivo.id, proyecto: basename(c.raiz) }]));
1692
+ /**
1693
+ * El mensaje `dispositivos` entero, compuesto en UN sitio: la foto, los ajustes leídos AHORA y
1694
+ * quién usa qué. Con cada emisor montando el suyo, uno se quedaba sin el campo nuevo —una
1695
+ * pestaña que reconecta, o una verificación, borrarían el «en uso» de la pantalla—.
1696
+ */
1697
+ const mensajeDeDispositivos = (informe) => ({
1698
+ clase: "dispositivos",
1699
+ informe,
1700
+ ajustes: ajustesDeDispositivos(),
1701
+ enUso: dispositivosEnUso(),
1702
+ });
1665
1703
  let deteccionEnVuelo;
1666
1704
  /**
1667
1705
  * Cómo acabó el último «Arrancar». Viaja con la foto porque el resultado de arrancar ES la
@@ -1678,10 +1716,24 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1678
1716
  deteccionEnVuelo = (async () => {
1679
1717
  try {
1680
1718
  informeDeDispositivos = sinRutas(await detectar());
1719
+ // Cada AVD necesita SU puerto, y fijado —también el único, en silencio—: una propuesta que
1720
+ // se recalculara al crear otro AVD le movería el puerto a uno que ya se usa. Se siembra
1721
+ // ANTES de emitir para que la foto salga con los puertos ya puestos, y releyendo después
1722
+ // (`ajustesDeDispositivos` va al disco): lo emitido es lo que quedó escrito. Un AVD recién
1723
+ // creado desde Ajustes recibe el suyo aquí, en la medida que cierra su trabajo.
1724
+ const pendientes = asignarPuertosPendientes(informeDeDispositivos.avds, ajustesDeDispositivos());
1725
+ if (pendientes !== undefined && opciones.guardarPuertosAsignados !== undefined) {
1726
+ try {
1727
+ opciones.guardarPuertosAsignados(pendientes);
1728
+ }
1729
+ catch (error) {
1730
+ // No impide emitir: la foto sigue siendo verdad, y sin puerto guardado el AVD usa el
1731
+ // de fábrica, que es lo que había antes. Lo que no se puede es callarlo.
1732
+ informar(`no se pudieron guardar los puertos de los emuladores (${codigoDe(error)})`);
1733
+ }
1734
+ }
1681
1735
  emitir({
1682
- clase: "dispositivos",
1683
- informe: informeDeDispositivos,
1684
- ajustes: ajustesDeDispositivos(),
1736
+ ...mensajeDeDispositivos(informeDeDispositivos),
1685
1737
  ...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
1686
1738
  });
1687
1739
  }
@@ -1732,7 +1784,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1732
1784
  }
1733
1785
  arranqueEnVuelo = avd;
1734
1786
  try {
1735
- const r = await arrancar(avd);
1787
+ // «Sin ventana» es un ajuste de ESE AVD (Ajustes → Dispositivos), leído al arrancar y no
1788
+ // del cliente: lo que se pulsa es «Arrancar», y cómo se arranca ya está elegido.
1789
+ const r = await arrancar(avd, { sinVentana: ajustesDeDispositivos().avds?.[avd]?.sinVentana === true });
1736
1790
  ultimoArranque = { avd, ...r };
1737
1791
  }
1738
1792
  catch (error) {
@@ -1797,7 +1851,80 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1797
1851
  ...informeDeDispositivos,
1798
1852
  dispositivos: informeDeDispositivos.dispositivos.map((d) => (d.id === id ? { ...d, verificado } : d)),
1799
1853
  };
1800
- emitir({ clase: "dispositivos", informe: informeDeDispositivos, ajustes: ajustesDeDispositivos() });
1854
+ emitir(mensajeDeDispositivos(informeDeDispositivos));
1855
+ };
1856
+ /**
1857
+ * **Lo de UN AVD: su puerto del túnel y si arranca sin ventana.**
1858
+ *
1859
+ * El nombre llega por el cable, así que tiene que estar en NUESTRA última medida (la misma
1860
+ * guarda que «Arrancar»): guardar ajustes de un AVD que no existe llenaría `settings.json` de
1861
+ * nombres que nadie ve. El puerto pasa por `motivoDePuertoInaceptable` contra los ajustes de
1862
+ * AHORA —dos AVD con el mismo puerto son el fallo que todo esto viene a quitar—, y la negativa
1863
+ * se dice sin guardar NADA del mensaje, tampoco el `sinVentana` que viniera con él: medio
1864
+ * cambio aplicado es un cambio que nadie pidió.
1865
+ *
1866
+ * No se vuelve a medir: el ajuste no cambia la máquina. Se reemite la foto que hay con los
1867
+ * ajustes releídos, que es lo que el campo tiene que enseñar.
1868
+ */
1869
+ const atenderAjusteDeAvd = async (avd, cambio) => {
1870
+ const guardar = opciones.guardarAjusteDeAvd;
1871
+ if (guardar === undefined) {
1872
+ informar("esta ejecución no puede guardar los ajustes de los emuladores");
1873
+ return;
1874
+ }
1875
+ if (informeDeDispositivos === undefined)
1876
+ await atenderDispositivos().catch(contar);
1877
+ if (informeDeDispositivos === undefined || !informeDeDispositivos.avds.includes(avd)) {
1878
+ informar(`no consta ningún AVD llamado «${avd}» en la última medida`);
1879
+ return;
1880
+ }
1881
+ if (cambio.puerto !== undefined) {
1882
+ const motivo = motivoDePuertoInaceptable(cambio.puerto, avd, ajustesDeDispositivos());
1883
+ if (motivo !== undefined) {
1884
+ informar(`no se cambió el puerto de ${avd}: ${motivo}`);
1885
+ return;
1886
+ }
1887
+ }
1888
+ try {
1889
+ guardar(avd, cambio);
1890
+ }
1891
+ catch (error) {
1892
+ informar(`no se pudieron guardar los ajustes de ${avd} (${codigoDe(error)})`);
1893
+ return;
1894
+ }
1895
+ emitir(mensajeDeDispositivos(informeDeDispositivos));
1896
+ };
1897
+ /**
1898
+ * **Parar un emulador**, por su serie de la última medida y solo si ES un emulador: la serie
1899
+ * acaba siendo un argumento de `adb`, y un físico no se «para» desde aquí. Al acabar se mide
1900
+ * SIEMPRE, como al arrancar: la foto es la que dice si se paró.
1901
+ */
1902
+ const atenderParadaDeEmulador = async (id) => {
1903
+ const parar = opciones.pararEmulador;
1904
+ if (parar === undefined) {
1905
+ informar("esta ejecución no puede parar emuladores");
1906
+ return;
1907
+ }
1908
+ if (informeDeDispositivos === undefined)
1909
+ await atenderDispositivos().catch(contar);
1910
+ const dispositivo = informeDeDispositivos?.dispositivos.find((d) => d.id === id);
1911
+ if (dispositivo === undefined || dispositivo.clase !== "emulador") {
1912
+ informar(`no consta ningún emulador «${id}» en la última medida`);
1913
+ return;
1914
+ }
1915
+ try {
1916
+ const r = await parar(dispositivo.id);
1917
+ if (!r.ok)
1918
+ informar(`no se pudo parar ${dispositivo.nombre}: ${r.detalle}`);
1919
+ }
1920
+ catch (error) {
1921
+ // El puerto promete no lanzar; si lo hace, se cuenta y se mide igual.
1922
+ informar(`no se pudo parar ${dispositivo.nombre} (${codigoDe(error)})`);
1923
+ }
1924
+ finally {
1925
+ informeDeDispositivos = undefined;
1926
+ await atenderDispositivos().catch(contar);
1927
+ }
1801
1928
  };
1802
1929
  const atenderCatalogo = async (proveedor) => {
1803
1930
  if (!proveedorConocido(proveedor))
@@ -2203,6 +2330,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2203
2330
  modelo: alAbrir?.modelo ?? SIN_CONSUMO,
2204
2331
  externo: alAbrir?.externo ?? SIN_CONSUMO,
2205
2332
  ventana: ventanaDeAhora(alAbrir?.contexto ?? 0),
2333
+ ...(alAbrir?.porModelo === undefined ? {} : { porModelo: alAbrir.porModelo }),
2206
2334
  });
2207
2335
  // Y la cola se vuelve a mirar: este proyecto queda bloqueado para las tareas —gana la
2208
2336
  // persona— y el que estuviera abierto antes acaba de quedar libre.
@@ -2925,7 +3053,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2925
3053
  // doble mal hecho, o un bug suyo en el camino síncrono— dejaría el recorrido en
2926
3054
  // `corriendo` para siempre y el botón muerto, que es justo lo que este `try` existe para
2927
3055
  // evitar. Su contrato es no lanzar nunca; esto es por si deja de cumplirlo.
2928
- const enCurso = lanzar({ dispositivo: medida.dispositivo, raiz: medida.abierto.raiz, app: medida.app }, { alFase });
3056
+ const enCurso = lanzar({
3057
+ dispositivo: medida.dispositivo,
3058
+ raiz: medida.abierto.raiz,
3059
+ app: medida.app,
3060
+ // El puerto LOCAL del túnel es el de SU AVD: con dos emuladores y el mismo puerto,
3061
+ // el `adb forward` del segundo le quita el túnel al primero sin error. Un físico no
3062
+ // tiene AVD y va por el de fábrica, que es lo que hace el lanzador sin puerto.
3063
+ ...(medida.dispositivo.avd === undefined
3064
+ ? {}
3065
+ : { puerto: puertoDeAvd(ajustesDeDispositivos(), medida.dispositivo.avd) }),
3066
+ }, { alFase });
2929
3067
  trabajo.cancelar = enCurso.cancelar;
2930
3068
  const resultado = await enCurso.terminado;
2931
3069
  // El último SIEMPRE se emite, aunque no haya pasado el plazo: es el que dice cómo acabó.
@@ -3673,40 +3811,25 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3673
3811
  ultimoProgreso = Date.now();
3674
3812
  };
3675
3813
  /**
3676
- * Lanza un paso, o cancela el que corre.
3814
+ * El cerrojo compartido: monta `trabajo`, emite el primer progreso y, al terminar, el último y
3815
+ * una medida nueva. Lo usan los pasos de receta y crear un AVD, que son la misma clase de
3816
+ * trabajo (`sdkmanager`/`avdmanager` sobre el mismo SDK): quien llama ya comprobó que no había
3817
+ * otro en marcha.
3677
3818
  *
3678
- * Al terminar se vuelve a MEDIR, y es lo que decide si el paso queda hecho: lo que diga el
3819
+ * Al terminar se vuelve a MEDIR, y es lo que decide si el trabajo queda hecho: lo que diga el
3679
3820
  * instalador es lo que él cree, y la foto es lo que hay. Es la misma regla que ya sigue
3680
3821
  * `instalar`.
3681
3822
  */
3682
- const atenderReceta = (mensaje) => {
3683
- if (mensaje.accion === "cancelar") {
3684
- trabajo?.cancelar();
3685
- return;
3686
- }
3687
- if (trabajo !== undefined) {
3688
- // Ya hay uno: se reenvía su estado en vez de lanzar otro.
3689
- emitirProgreso("corriendo");
3690
- return;
3691
- }
3692
- const correr = opciones.correrPasoDeReceta;
3693
- if (correr === undefined) {
3694
- informar("esta ejecución no puede ejecutar pasos de instalación");
3695
- return;
3696
- }
3697
- const enMarcha = correr(mensaje.id, mensaje.paso, (linea) => {
3698
- if (trabajo === undefined)
3699
- return;
3700
- trabajo.lineas.push(linea);
3701
- // A ritmo: cada emisión manda la cola entera, y por línea sería cuadrático en bytes
3702
- // con un `sdkmanager` que habla cada pocos milisegundos. Es la misma razón que
3703
- // `MS_ENTRE_PARCIALES` en la piel web.
3704
- if (Date.now() - ultimoProgreso >= MS_ENTRE_PROGRESOS)
3705
- emitirProgreso("corriendo");
3706
- });
3823
+ const correrTrabajo = (receta, paso, enMarcha,
3824
+ /**
3825
+ * Lo que el servidor apunta al acabar (la procedencia de un AVD nuevo, los ajustes de uno
3826
+ * eliminado), ANTES del último progreso y de la medida: así la foto que sigue ya lo trae.
3827
+ * Un fallo suyo se dice y no tumba el cierre del trabajo.
3828
+ */
3829
+ alAcabar) => {
3707
3830
  trabajo = {
3708
- receta: mensaje.id,
3709
- paso: mensaje.paso,
3831
+ receta,
3832
+ paso,
3710
3833
  titulo: enMarcha.titulo,
3711
3834
  lineas: [],
3712
3835
  cancelar: enMarcha.cancelar,
@@ -3715,6 +3838,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3715
3838
  emitirProgreso("corriendo");
3716
3839
  void (async () => {
3717
3840
  const resultado = await enMarcha.terminado;
3841
+ try {
3842
+ alAcabar?.(resultado);
3843
+ }
3844
+ catch (error) {
3845
+ informar(`no se pudo apuntar lo que cambió al acabar (${codigoDe(error)})`);
3846
+ }
3718
3847
  // El último progreso SIEMPRE se emite, aunque no haya pasado el plazo: es el que
3719
3848
  // completa el log y el que dice cómo acabó.
3720
3849
  ultimoProgreso = 0;
@@ -3725,6 +3854,145 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3725
3854
  await atenderDispositivos().catch(contar);
3726
3855
  })();
3727
3856
  };
3857
+ /**
3858
+ * Cada línea de la salida, a la cola del trabajo en curso y emitida A RITMO: cada emisión manda
3859
+ * la cola entera, y por línea sería cuadrático en bytes con un `sdkmanager` que habla cada
3860
+ * pocos milisegundos. Es la misma razón que `MS_ENTRE_PARCIALES` en la piel web.
3861
+ */
3862
+ const alSalirLineaDelTrabajo = (linea) => {
3863
+ if (trabajo === undefined)
3864
+ return;
3865
+ trabajo.lineas.push(linea);
3866
+ if (Date.now() - ultimoProgreso >= MS_ENTRE_PROGRESOS)
3867
+ emitirProgreso("corriendo");
3868
+ };
3869
+ /** Lanza un paso, o cancela el que corre. */
3870
+ const atenderReceta = (mensaje) => {
3871
+ if (mensaje.accion === "cancelar") {
3872
+ trabajo?.cancelar();
3873
+ return;
3874
+ }
3875
+ if (trabajo !== undefined) {
3876
+ // Ya hay uno: se reenvía su estado en vez de lanzar otro.
3877
+ emitirProgreso("corriendo");
3878
+ return;
3879
+ }
3880
+ const correr = opciones.correrPasoDeReceta;
3881
+ if (correr === undefined) {
3882
+ informar("esta ejecución no puede ejecutar pasos de instalación");
3883
+ return;
3884
+ }
3885
+ correrTrabajo(mensaje.id, mensaje.paso, correr(mensaje.id, mensaje.paso, alSalirLineaDelTrabajo));
3886
+ };
3887
+ /** ¿Corre ahora el AVD `avd`, según la ÚLTIMA medida? Todo lo que no es «apagado» o «no disponible» cuenta. */
3888
+ const baseEnMarcha = (avd) => (informeDeDispositivos?.dispositivos ?? []).some((d) => d.avd === avd && d.estado !== "apagado" && d.estado !== "no-disponible");
3889
+ /**
3890
+ * **Crear un AVD con nombre**, por el MISMO cerrojo que las recetas y contado igual
3891
+ * (`receta: "crear-avd"`, `paso: 0`): la pantalla ya sabe pintar ese log.
3892
+ *
3893
+ * El nombre acaba siendo un argumento de `avdmanager`, así que se valida aquí con la regla
3894
+ * pura (forma, largo, y que no exista ya en la última medida) antes de lanzar nada; sin medida
3895
+ * no se puede saber si se repite, así que se mide primero. El cerrojo se vuelve a mirar
3896
+ * DESPUÉS de esa espera: mientras se medía pudo arrancar otro trabajo.
3897
+ */
3898
+ const atenderCrearEmulador = async (nombre, base, conDatos) => {
3899
+ if (trabajo !== undefined) {
3900
+ emitirProgreso("corriendo");
3901
+ return;
3902
+ }
3903
+ const crear = opciones.crearAvd;
3904
+ if (crear === undefined) {
3905
+ informar("esta ejecución no puede crear emuladores");
3906
+ return;
3907
+ }
3908
+ if (informeDeDispositivos === undefined)
3909
+ await atenderDispositivos().catch(contar);
3910
+ if (informeDeDispositivos === undefined) {
3911
+ informar("no se creó el emulador: todavía no hay una medida con la que comprobar el nombre");
3912
+ return;
3913
+ }
3914
+ const motivo = motivoDeNombreDeAvdInaceptable(nombre, informeDeDispositivos.avds);
3915
+ if (motivo !== undefined) {
3916
+ informar(`no se creó el emulador: ${motivo}`);
3917
+ return;
3918
+ }
3919
+ if (base !== undefined) {
3920
+ // La base es un NOMBRE que acaba en una ruta: tiene que ser uno de los que la medida vio.
3921
+ if (!informeDeDispositivos.avds.includes(base)) {
3922
+ informar(`no se creó el emulador: ${base} no es un emulador que conozcamos`);
3923
+ return;
3924
+ }
3925
+ // Clonar copia discos que la base tiene bloqueados mientras corre (`multiinstance.lock`);
3926
+ // copiar solo la configuración no los toca y vale con ella encendida.
3927
+ if (conDatos === true && baseEnMarcha(base)) {
3928
+ informar(`no se creó el emulador: ${base} está en marcha; apágalo para clonarlo con lo instalado`);
3929
+ return;
3930
+ }
3931
+ }
3932
+ if (trabajo !== undefined) {
3933
+ emitirProgreso("corriendo");
3934
+ return;
3935
+ }
3936
+ correrTrabajo("crear-avd", 0, crear(nombre, alSalirLineaDelTrabajo, base === undefined ? undefined : { base, conDatos: conDatos === true }),
3937
+ // La procedencia se GUARDA al acabar bien: «copia de X» (configuración) o «clon de X» (con lo
3938
+ // instalado). Sin base no hay de quién, y un fallo no deja AVD al que apuntarla.
3939
+ base === undefined
3940
+ ? undefined
3941
+ : (r) => {
3942
+ if (r.estado !== "ok")
3943
+ return;
3944
+ opciones.guardarAjusteDeAvd?.(nombre, { copiaDe: base, ...(conDatos === true ? { clon: true } : {}) });
3945
+ });
3946
+ };
3947
+ /**
3948
+ * **Eliminar un AVD**, por el MISMO cerrojo que crear (`receta: "borrar-avd"`, `paso: 0`). El
3949
+ * nombre llega por el cable y acaba en un argumento de `avdmanager`, así que tiene que ser uno de
3950
+ * los de NUESTRA última medida. Tres negativas, en orden, y cada una se DICE (la del navegador no
3951
+ * llega de vuelta: el cliente aplica las mismas reglas antes y no deja pulsar):
3952
+ * - nunca el ÚLTIMO: es la base de las copias, y siempre tiene que quedar uno;
3953
+ * - nunca uno EN MARCHA (el mismo criterio que `baseEnMarcha`): el emulador tiene sus discos
3954
+ * bloqueados y borrar la carpeta por debajo lo dejaría roto.
3955
+ * Al acabar BIEN se quitan también sus ajustes (puerto, sin ventana, procedencia) para que su
3956
+ * puerto quede libre, y `correrTrabajo` vuelve a medir.
3957
+ */
3958
+ const atenderEliminarEmulador = async (avd) => {
3959
+ if (trabajo !== undefined) {
3960
+ emitirProgreso("corriendo");
3961
+ return;
3962
+ }
3963
+ const borrar = opciones.borrarAvd;
3964
+ if (borrar === undefined) {
3965
+ informar("esta ejecución no puede eliminar emuladores");
3966
+ return;
3967
+ }
3968
+ if (informeDeDispositivos === undefined)
3969
+ await atenderDispositivos().catch(contar);
3970
+ if (informeDeDispositivos === undefined) {
3971
+ informar("no se eliminó el emulador: todavía no hay una medida con la que comprobarlo");
3972
+ return;
3973
+ }
3974
+ if (!informeDeDispositivos.avds.includes(avd)) {
3975
+ informar(`no se eliminó el emulador: ${avd} no es un emulador que conozcamos`);
3976
+ return;
3977
+ }
3978
+ if (informeDeDispositivos.avds.length <= 1) {
3979
+ informar(`no se eliminó ${avd}: es el único: siempre tiene que quedar al menos uno`);
3980
+ return;
3981
+ }
3982
+ if (baseEnMarcha(avd)) {
3983
+ informar(`no se eliminó ${avd}: está en marcha; apágalo para eliminarlo`);
3984
+ return;
3985
+ }
3986
+ // Mientras se medía pudo arrancar otro trabajo.
3987
+ if (trabajo !== undefined) {
3988
+ emitirProgreso("corriendo");
3989
+ return;
3990
+ }
3991
+ correrTrabajo("borrar-avd", 0, borrar(avd, alSalirLineaDelTrabajo), (r) => {
3992
+ if (r.estado === "ok")
3993
+ opciones.quitarAjustesDeAvd?.(avd);
3994
+ });
3995
+ };
3728
3996
  /**
3729
3997
  * Los modelos de un motor externo, para el desplegable de un subagente.
3730
3998
  *
@@ -3911,7 +4179,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3911
4179
  // contador a pintar un cero que nadie ha medido.
3912
4180
  if (c === undefined)
3913
4181
  return;
3914
- emitir({ clase: "consumo", modelo: c.modelo, externo: c.externo, ventana: ventanaDeAhora(c.contexto) });
4182
+ emitir({
4183
+ clase: "consumo",
4184
+ modelo: c.modelo,
4185
+ externo: c.externo,
4186
+ ventana: ventanaDeAhora(c.contexto),
4187
+ ...(c.porModelo === undefined ? {} : { porModelo: c.porModelo }),
4188
+ });
3915
4189
  };
3916
4190
  /**
3917
4191
  * Cuánto ocupa la ventana y cuál es su tope, si se sabe.
@@ -4888,6 +5162,51 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
4888
5162
  respuesta.end();
4889
5163
  return;
4890
5164
  }
5165
+ if (typeof mensaje === "object" &&
5166
+ mensaje !== null &&
5167
+ mensaje.clase === "eliminarEmulador" &&
5168
+ typeof mensaje.avd === "string") {
5169
+ void atenderEliminarEmulador(mensaje.avd).catch(contar);
5170
+ respuesta.writeHead(204);
5171
+ respuesta.end();
5172
+ return;
5173
+ }
5174
+ if (typeof mensaje === "object" &&
5175
+ mensaje !== null &&
5176
+ mensaje.clase === "crearEmulador" &&
5177
+ typeof mensaje.nombre === "string" &&
5178
+ (mensaje.base === undefined || typeof mensaje.base === "string") &&
5179
+ (mensaje.conDatos === undefined || typeof mensaje.conDatos === "boolean")) {
5180
+ // Suelto, como la receta: el progreso y la foto de después viajan por el SSE.
5181
+ void atenderCrearEmulador(mensaje.nombre, mensaje.base, mensaje.conDatos).catch(contar);
5182
+ respuesta.writeHead(204);
5183
+ respuesta.end();
5184
+ return;
5185
+ }
5186
+ if (typeof mensaje === "object" &&
5187
+ mensaje !== null &&
5188
+ mensaje.clase === "ajusteDeAvd" &&
5189
+ typeof mensaje.avd === "string" &&
5190
+ (mensaje.puerto === undefined || typeof mensaje.puerto === "number") &&
5191
+ (mensaje.sinVentana === undefined || typeof mensaje.sinVentana === "boolean")) {
5192
+ // Campo a campo: lo que no es un número o un booleano no llega a `settings.json`.
5193
+ void atenderAjusteDeAvd(mensaje.avd, {
5194
+ ...(mensaje.puerto === undefined ? {} : { puerto: mensaje.puerto }),
5195
+ ...(mensaje.sinVentana === undefined ? {} : { sinVentana: mensaje.sinVentana }),
5196
+ }).catch(contar);
5197
+ respuesta.writeHead(204);
5198
+ respuesta.end();
5199
+ return;
5200
+ }
5201
+ if (typeof mensaje === "object" &&
5202
+ mensaje !== null &&
5203
+ mensaje.clase === "pararEmulador" &&
5204
+ typeof mensaje.id === "string") {
5205
+ void atenderParadaDeEmulador(mensaje.id).catch(contar);
5206
+ respuesta.writeHead(204);
5207
+ respuesta.end();
5208
+ return;
5209
+ }
4891
5210
  /**
4892
5211
  * ANTES del `recibir` de la consola, y también antes que las demás clases de tarea: por
4893
5212
  * aquí no entra nada hacia ningún turno. Ver `atenderMirar`.
@@ -4904,6 +5223,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
4904
5223
  return;
4905
5224
  }
4906
5225
  if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "tarea") {
5226
+ if (mensaje.accion === "crear" && mudandoWorkspace) {
5227
+ // Una tarea creada a mitad de la mudanza guardaría la raíz VIEJA, que está a punto de
5228
+ // borrarse. Se niega con motivo, como cualquier otra negativa del cable.
5229
+ respuesta.writeHead(409, { "content-type": "application/json" });
5230
+ respuesta.end(JSON.stringify({ motivo: "se está cambiando la carpeta de los proyectos: crea la tarea cuando termine" }));
5231
+ return;
5232
+ }
4907
5233
  if (mensaje.accion === "crear" && opciones.colaDeTareas !== undefined) {
4908
5234
  const creada = atenderCrearTarea(mensaje.proyecto, mensaje.peticion, mensaje.encargo, mensaje.borrador);
4909
5235
  if (creada !== undefined) {
@@ -4964,9 +5290,84 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
4964
5290
  * Y se reemite SIEMPRE, se haya escrito o no: con el valor nuevo cuando valió y con el
4965
5291
  * de antes cuando no, así el campo acaba enseñando lo que hay en disco y no lo que se
4966
5292
  * tecleó. Un rechazo mudo dejaría la pantalla afirmando un ajuste que no está puesto.
5293
+ *
5294
+ * **Y cambiarla MUDA lo que ya está bajado**, en dos actos (`vestibulo.mudarWorkspace`):
5295
+ * `planear` contesta en la PROPIA respuesta qué se movería —por nombre y entorno, nunca
5296
+ * una ruta— o por qué no se puede, sin tocar nada; `aplicar` (o sin `accion`) contesta
5297
+ * 204 en el acto y el recorrido llega por `mudanzaDeWorkspace`, porque copiar y
5298
+ * verificar cientos de megas no cabe en una petición. La negativa que se sabe ANTES
5299
+ * de empezar —una ruta que no vale, una mudanza ya en marcha— va en un 409 con motivo.
4967
5300
  */
4968
- opciones.guardarWorkspace?.(mensaje.ruta);
4969
- emitirWorkspace();
5301
+ const negarWorkspace = (motivo) => {
5302
+ respuesta.writeHead(409, { "content-type": "application/json" });
5303
+ respuesta.end(JSON.stringify({ motivo }));
5304
+ };
5305
+ const tareasVivas = () => (opciones.colaDeTareas?.listar() ?? []).map((t) => ({ estado: t.estado, raiz: t.proyecto.raiz }));
5306
+ const resuelta = opciones.resolverWorkspace?.(mensaje.ruta);
5307
+ if (resuelta === undefined || opciones.casaXonecode === undefined) {
5308
+ // Esta ejecución no sabe mudar: el comportamiento de siempre, solo escribir el ajuste.
5309
+ opciones.guardarWorkspace?.(mensaje.ruta);
5310
+ emitirWorkspace();
5311
+ respuesta.writeHead(204);
5312
+ respuesta.end();
5313
+ return;
5314
+ }
5315
+ if ("motivo" in resuelta) {
5316
+ emitirWorkspace();
5317
+ negarWorkspace(resuelta.motivo);
5318
+ return;
5319
+ }
5320
+ if (mensaje.accion === "planear") {
5321
+ const plan = vestibulo.planearWorkspace(resuelta.ruta, opciones.casaXonecode, tareasVivas());
5322
+ let megas;
5323
+ if (plan.motivo === undefined && plan.mudanzas.length > 0) {
5324
+ megas = await megasDeLasMudanzas(plan.mudanzas).catch(() => undefined);
5325
+ }
5326
+ respuesta.writeHead(200, { "content-type": "application/json" });
5327
+ respuesta.end(JSON.stringify({
5328
+ proyectos: plan.mudanzas.map((m) => ({ entorno: m.entorno, proyecto: m.proyecto })),
5329
+ ...(megas === undefined ? {} : { megas }),
5330
+ ...(plan.motivo === undefined ? {} : { motivo: plan.motivo }),
5331
+ }));
5332
+ return;
5333
+ }
5334
+ if (mudandoWorkspace) {
5335
+ negarWorkspace("ya hay un cambio de carpeta en marcha: espera a que termine");
5336
+ return;
5337
+ }
5338
+ mudandoWorkspace = true;
5339
+ const casaXonecode = opciones.casaXonecode;
5340
+ const guardar = opciones.guardarWorkspace ?? (() => { });
5341
+ void (async () => {
5342
+ try {
5343
+ const { resultado, cerroLaAbierta } = await vestibulo.mudarWorkspace({
5344
+ hacia: resuelta.ruta,
5345
+ casaXonecode,
5346
+ tareas: tareasVivas,
5347
+ guardar,
5348
+ progreso: (progreso) => {
5349
+ ultimoProgresoDeMudanza = progreso;
5350
+ emitir({ clase: "mudanzaDeWorkspace", progreso });
5351
+ },
5352
+ });
5353
+ // El cable se quedaría enganchado a una consola cerrada: al vestíbulo, como al
5354
+ // borrar una copia. El alta siguiente ya trae las copias en la base nueva.
5355
+ if (cerroLaAbierta)
5356
+ adjuntar();
5357
+ emitir({ clase: "mudanzaDeWorkspace", resultado });
5358
+ }
5359
+ catch (error) {
5360
+ contar(error);
5361
+ emitir({ clase: "mudanzaDeWorkspace", resultado: { estado: "rechazado", motivo: "no se pudo cambiar la carpeta" } });
5362
+ }
5363
+ finally {
5364
+ mudandoWorkspace = false;
5365
+ ultimoProgresoDeMudanza = undefined;
5366
+ emitirWorkspace();
5367
+ opciones.revisarTareas?.();
5368
+ await anunciarAlta().catch(contar);
5369
+ }
5370
+ })().catch(contar);
4970
5371
  respuesta.writeHead(204);
4971
5372
  respuesta.end();
4972
5373
  return;
@@ -5173,6 +5574,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
5173
5574
  // Suelto: `anunciarAlta` consulta CloudStudio y el `POST` no puede quedarse
5174
5575
  // esperando por eso. El alta nueva llega por el SSE, como siempre.
5175
5576
  void anunciarAlta().catch(contar);
5577
+ // Y la foto que hay, sin volver a medir: lo que cambió es QUIÉN usa ese aparato, y
5578
+ // Ajustes lo enseña al lado de cada uno (`enUso`).
5579
+ if (informeDeDispositivos !== undefined)
5580
+ emitir(mensajeDeDispositivos(informeDeDispositivos));
5176
5581
  }
5177
5582
  respuesta.writeHead(204);
5178
5583
  respuesta.end();
@@ -5696,6 +6101,12 @@ export function ajusteDeWorkspaceCableado(opciones) {
5696
6101
  const guardar = opciones.guardar ?? ((ruta) => void guardarWorkspaceEnDisco(undefined, ruta));
5697
6102
  return {
5698
6103
  workspace: () => leer() ?? baseDeWorkspacePorOmision(),
6104
+ casaXonecode: join(opciones.casa, ".xonecode"),
6105
+ resolverWorkspace: (ruta) => {
6106
+ const absoluta = expandirConCasa(ruta, opciones.casa);
6107
+ const motivo = motivoDeWorkspaceInaceptable(absoluta);
6108
+ return motivo === undefined ? { ruta: absoluta } : { motivo };
6109
+ },
5699
6110
  guardarWorkspace: (ruta) => {
5700
6111
  const absoluta = expandirConCasa(ruta, opciones.casa);
5701
6112
  if (motivoDeWorkspaceInaceptable(absoluta) !== undefined)
@@ -5704,6 +6115,31 @@ export function ajusteDeWorkspaceCableado(opciones) {
5704
6115
  },
5705
6116
  };
5706
6117
  }
6118
+ /**
6119
+ * Los siete puertos de los emuladores, cableados con las funciones de verdad — extraídos por el
6120
+ * MISMO motivo que `ajusteDeWorkspaceCableado`: dentro de `arrancarConsolaWeb` serían un literal
6121
+ * que ningún test mira, y cada uno es un campo OPCIONAL de `montarRutas`, así que olvidarlo o
6122
+ * perder un argumento por el camino compila y pasa.
6123
+ *
6124
+ * `casa` es la del `settings.json` donde se guardan los ajustes por AVD; ausente, la del usuario
6125
+ * (`guardarAjusteDeAvd(undefined, …)`). `arrancar` entra solo para que un test vea qué recibe la
6126
+ * de verdad sin lanzar un emulador.
6127
+ */
6128
+ export function emuladoresCableados(opciones = {}) {
6129
+ const arrancar = opciones.arrancar ?? arrancarEmulador;
6130
+ return {
6131
+ arrancarEmulador: (avd, o) => arrancar(avd, {}, o ?? {}),
6132
+ // Con `conDatos` se CLONA la carpeta; sin él, solo la configuración de la base sobre un AVD vacío.
6133
+ crearAvd: (nombre, alSalirLinea, desde) => desde?.conDatos === true
6134
+ ? clonarAvd(desde.base, nombre, { alSalirLinea })
6135
+ : crearAvd(nombre, { alSalirLinea }, desde === undefined ? {} : { base: desde.base }),
6136
+ borrarAvd: (nombre, alSalirLinea) => borrarAvd(nombre, { alSalirLinea }),
6137
+ pararEmulador: (serie) => pararEmulador(serie),
6138
+ guardarAjusteDeAvd: (avd, cambio) => void guardarAjusteDeAvd(opciones.casa, avd, cambio),
6139
+ quitarAjustesDeAvd: (avd) => void quitarAjustesDeAvd(opciones.casa, avd),
6140
+ guardarPuertosAsignados: (asignados) => void guardarPuertosAsignados(opciones.casa, asignados),
6141
+ };
6142
+ }
5707
6143
  /**
5708
6144
  * Las dos mitades de la casilla «Depurar», cableadas — extraídas por el MISMO motivo que
5709
6145
  * `ajusteDeWorkspaceCableado`. `leer`/`guardar` entran por parámetro, y no como en el workspace
@@ -5756,8 +6192,28 @@ export function ajusteDeGestorCableado(ajuste) {
5756
6192
  jira: crearGestorJira,
5757
6193
  notion: crearGestorNotion,
5758
6194
  };
6195
+ const sinServicio = () => {
6196
+ throw new Error("esta ejecución no tiene conectores");
6197
+ };
5759
6198
  return {
5760
6199
  conectores: (alCambiar) => (servicio = ajuste.conectores(alCambiar)),
6200
+ conectoresDeSesion: {
6201
+ delProyecto: (raiz) => {
6202
+ if (servicio === undefined)
6203
+ return [];
6204
+ const proyecto = cargar(raiz).config.proyecto;
6205
+ const lista = servicio.lista();
6206
+ return conectoresParaElAgente({
6207
+ marcados: proyecto?.conectores ?? [],
6208
+ gestor: proyecto?.gestorDeTareas?.conector,
6209
+ anadidos: lista.conectores.map((c) => c.id),
6210
+ catalogo: lista.catalogo,
6211
+ });
6212
+ },
6213
+ // `async`: sin servicio, el fallo sale como promesa RECHAZADA, que es lo que la tool devuelve como texto.
6214
+ tools: async (id) => (servicio === undefined ? sinServicio() : servicio.tools(id)),
6215
+ llamar: async (id, nombre, args, opciones) => (servicio === undefined ? sinServicio() : servicio.llamar(id, nombre, args, opciones)),
6216
+ },
5761
6217
  gestorDeTareas: (conector) => {
5762
6218
  const fabrica = Object.hasOwn(FABRICAS, conector) ? FABRICAS[conector] : undefined;
5763
6219
  if (fabrica === undefined)
@@ -6005,7 +6461,10 @@ export async function arrancarConsolaWeb(opciones) {
6005
6461
  escribir(`${texto}\n`);
6006
6462
  vestibulo?.consola.consola.escribir(`${texto}\n`);
6007
6463
  };
6008
- vestibulo = opciones.vestibulo ?? vestibuloReal(opciones, servidor, escribir, informar);
6464
+ // Los conectores y el gestor de tareas, sobre UN servicio —construido luego por `montarRutas`—, y
6465
+ // las sesiones sobre ese mismo: por eso esto va ANTES del vestíbulo, que es quien las abre.
6466
+ const conectoresYGestor = ajusteDeGestorCableado(ajusteDeConectoresCableado({ casa: homedir() }));
6467
+ vestibulo = opciones.vestibulo ?? vestibuloReal(opciones, servidor, escribir, informar, conectoresYGestor.conectoresDeSesion);
6009
6468
  const conVestibulo = vestibulo;
6010
6469
  /**
6011
6470
  * El corredor de tareas y su costura con el cable — `construirCorredorDeTareasCableado`
@@ -6098,7 +6557,8 @@ export async function arrancarConsolaWeb(opciones) {
6098
6557
  // (`ajusteDeConectoresCableado`), nunca un literal aquí dentro.
6099
6558
  // Y el gestor de tareas (IXCODE-11) llama por ESE mismo servicio: los dos salen de
6100
6559
  // `ajusteDeGestorCableado`, que envuelve la fábrica de conectores.
6101
- ...ajusteDeGestorCableado(ajusteDeConectoresCableado({ casa: homedir() })),
6560
+ conectores: conectoresYGestor.conectores,
6561
+ gestorDeTareas: conectoresYGestor.gestorDeTareas,
6102
6562
  // El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
6103
6563
  // monta y el botón no llega a existir.
6104
6564
  ...(haySelectorDeCarpeta()
@@ -6166,7 +6626,12 @@ export async function arrancarConsolaWeb(opciones) {
6166
6626
  // nueve veces, y la que el comentario de justo abajo describe para las cuatro del
6167
6627
  // lanzamiento. `montarRutas` lo trata como ausente si falta, así que olvidarlo aquí
6168
6628
  // dejaría el botón diciendo «esta ejecución no puede arrancar emuladores» con todo verde.
6169
- arrancarEmulador: (avd) => arrancarEmulador(avd),
6629
+ // Los cinco de los emuladores —arrancar, crear, parar, y guardar lo de cada AVD—, con las
6630
+ // funciones REALES y compuestos fuera (`emuladoresCableados`) para que la composición tenga
6631
+ // test: `arrancarEmulador: (avd) => arrancarEmulador(avd)` compilaba igual perdiendo el
6632
+ // «sin ventana», y un campo opcional olvidado aquí deja el botón diciendo «esta ejecución no
6633
+ // puede» con todo en verde.
6634
+ ...emuladoresCableados(),
6170
6635
  /**
6171
6636
  * Las cuatro de «¿se puede lanzar y lánzalo?», compuestas con las funciones REALES.
6172
6637
  *
@@ -6327,10 +6792,10 @@ function esperarInterrupcion() {
6327
6792
  }
6328
6793
  /** El vestíbulo con todas sus piezas reales. Se construye DESPUÉS del servidor porque el
6329
6794
  * callback de OAuth necesita saber a qué URL devolver al navegador. */
6330
- function vestibuloReal(opciones, servidor, escribir, informar) {
6795
+ function vestibuloReal(opciones, servidor, escribir, informar, conectores) {
6331
6796
  // Lo PRIMERO, antes de leer nada de disco: si esto va a fallar, que falle sin haber
6332
6797
  // aplicado credenciales al proceso ni construido medio vestíbulo.
6333
- const ejecutor = banderaDeEjecutor(opciones);
6798
+ const ejecutor = banderaDeEjecutor(opciones, conectores);
6334
6799
  const cargado = cargar(opciones.cwd);
6335
6800
  aplicarAuth(cargado.auth);
6336
6801
  // La lectura NO se hace aquí: se hace al abrir cada consola, que es lo que hace que una
@@ -6441,12 +6906,18 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
6441
6906
  * O el ejecutor real, o el de pega PEDIDO con `--guion`, o un error. Lo que no hay es una
6442
6907
  * tercera opción muda.
6443
6908
  */
6444
- function banderaDeEjecutor(opciones) {
6909
+ export function banderaDeEjecutor(opciones, conectores) {
6445
6910
  if (opciones.guion === true)
6446
6911
  return {};
6447
- if (opciones.crearEjecutor === undefined) {
6912
+ const crear = opciones.crearEjecutor;
6913
+ if (crear === undefined) {
6448
6914
  throw new Error("esta consola web se montó sin `crearEjecutor`: correría el agente de pega sin decirlo (usa --guion si es lo que quieres)");
6449
6915
  }
6450
- return { crearEjecutor: opciones.crearEjecutor };
6916
+ // Los conectores se AÑADEN aquí, a todas las consolas —de persona y de tarea—: el vestíbulo no
6917
+ // tiene que acordarse de pasarlos en cada puerta. En una tarea, lo que escriben se rechaza igual
6918
+ // (`consolaDeTarea.ts`).
6919
+ if (conectores === undefined)
6920
+ return { crearEjecutor: crear };
6921
+ return { crearEjecutor: (alAbrir, o) => crear(alAbrir, { ...o, conectores }) };
6451
6922
  }
6452
6923
  //# sourceMappingURL=arranque.js.map