xonecode 0.6.0 → 0.8.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.
- package/apps/web/dist/assets/{index-rqecK6s2.css → index-Boak51UP.css} +1 -1
- package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
- package/apps/web/dist/assets/{php-BBij3Lhc.js → php-Cu6F_QCJ.js} +1 -1
- package/apps/web/dist/assets/{ruby-Dq2YEaLo.js → ruby-BiqQPoC6.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/cloudstudioClient.js +10 -0
- package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -1
- package/dist/agent/cloudstudio/descarga.js +14 -4
- package/dist/agent/cloudstudio/descarga.js.map +1 -1
- package/dist/agent/cloudstudio/ramaActiva.js +11 -0
- package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
- package/dist/agent/cloudstudio/subida.js +13 -4
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/config/catalogoModelos.js +45 -0
- package/dist/agent/config/catalogoModelos.js.map +1 -1
- package/dist/agent/config/modelos.js +103 -12
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/config/mudanzaEnDisco.js +121 -0
- package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
- package/dist/agent/config/selectorEnMaquina.js +45 -0
- package/dist/agent/config/selectorEnMaquina.js.map +1 -0
- package/dist/agent/config/settingsEnDisco.js +35 -0
- package/dist/agent/config/settingsEnDisco.js.map +1 -1
- package/dist/agent/dispositivos/juezVisual.js +152 -0
- package/dist/agent/dispositivos/juezVisual.js.map +1 -0
- package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
- package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
- package/dist/agent/exportarPdf.js +140 -0
- package/dist/agent/exportarPdf.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +115 -0
- package/dist/agent/grafo/criticaVisual.js.map +1 -0
- package/dist/agent/grafo/navegacionXone.js +37 -4
- package/dist/agent/grafo/navegacionXone.js.map +1 -1
- package/dist/agent/grafo/perfiles.js +52 -9
- package/dist/agent/grafo/perfiles.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +74 -7
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/grafo/skills.js +32 -0
- package/dist/agent/grafo/skills.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +103 -13
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/navegacion/estilosEnDisco.js +110 -0
- package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/hechosEnDisco.js +26 -0
- package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/indiceEnDisco.js +15 -0
- package/dist/agent/navegacion/indiceEnDisco.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +85 -24
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +53 -0
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/pluginDeSkills.js +99 -0
- package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
- package/dist/agent/subagentes/subagenteExterno.js +48 -0
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/subagentes/subagenteOpencode.js +53 -1
- package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
- package/dist/agent/tareas/juezDeTarea.js +1 -1
- package/dist/agent/tareas/juezDeTarea.js.map +1 -1
- package/dist/agent/tareas/tareasEnDisco.js +49 -0
- package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
- package/dist/agent/turno/diagnosticoDeTools.js +5 -1
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
- package/dist/agent/turno/informeDeTraza.js +27 -4
- package/dist/agent/turno/informeDeTraza.js.map +1 -1
- package/dist/agent/turno/puente.js +24 -1
- package/dist/agent/turno/puente.js.map +1 -1
- package/dist/agent/turno/resumenDeContexto.js +88 -4
- package/dist/agent/turno/resumenDeContexto.js.map +1 -1
- package/dist/agent/turno/resumenDeTool.js +18 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/agent/turno/rubrica.js +91 -0
- package/dist/agent/turno/rubrica.js.map +1 -0
- package/dist/agent/turno/turnoReal.js +172 -11
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/consola.js +63 -0
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +51 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.js +8 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/core/agentes.js +111 -9
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/esfuerzo.js +141 -0
- package/dist/core/esfuerzo.js.map +1 -0
- package/dist/core/estilos.js +78 -0
- package/dist/core/estilos.js.map +1 -0
- package/dist/core/exportacion.js +140 -0
- package/dist/core/exportacion.js.map +1 -0
- package/dist/core/hechosDelProyecto.js +87 -0
- package/dist/core/hechosDelProyecto.js.map +1 -0
- package/dist/core/logDeDispositivo.js +143 -0
- package/dist/core/logDeDispositivo.js.map +1 -0
- package/dist/core/modelos.js +32 -0
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/mudanzaDeWorkspace.js +93 -0
- package/dist/core/mudanzaDeWorkspace.js.map +1 -0
- package/dist/core/planes.js +70 -0
- package/dist/core/planes.js.map +1 -0
- package/dist/core/ports.js +4 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/rubrica.js +90 -0
- package/dist/core/rubrica.js.map +1 -0
- package/dist/core/selectorDeCarpeta.js +87 -0
- package/dist/core/selectorDeCarpeta.js.map +1 -0
- package/dist/core/settings.js +54 -5
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +45 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/core/topeAgotado.js +69 -0
- package/dist/core/topeAgotado.js.map +1 -0
- package/dist/web/servidor/arranque.js +324 -9
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/sesiones.js +30 -2
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +55 -6
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +2 -1
- package/skills/xone-hotswap/SKILL.md +31 -9
- package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
- package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
- package/skills/xone-hotswap/scripts/xone-desplegar-android +28 -0
- package/skills/xone-hotswap/scripts/xone-hotswap +39 -9
- package/skills/xone-hotswap/scripts/xone-reiniciar-android +28 -0
- package/skills/xone-plan-builder/SKILL.md +22 -4
- package/skills/xone-spec-builder/SKILL.md +37 -7
- package/apps/web/dist/assets/index-DBZTodio.js +0 -477
|
@@ -62,8 +62,10 @@ import { aplicarAuth, aplicarCredencialAlProceso, cargar, guardarModeloGlobal, }
|
|
|
62
62
|
import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDisco.js";
|
|
63
63
|
import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
|
|
64
64
|
import { crearCheckpointerDeProyecto, hayCheckpoint, olvidarHilo, } from "../../agent/sesiones/checkpointer.js";
|
|
65
|
-
import { cargarSettings, guardarConcurrenciaDeTareas, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/config/settingsEnDisco.js";
|
|
66
|
-
import { dentroDelWorkspace, seAplicaSinAprobacion } from "../../core/settings.js";
|
|
65
|
+
import { cargarSettings, guardarConcurrenciaDeTareas, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/config/settingsEnDisco.js";
|
|
66
|
+
import { dentroDelWorkspace, expandirConCasa, motivoDeWorkspaceInaceptable, seAplicaSinAprobacion, } from "../../core/settings.js";
|
|
67
|
+
import { mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
|
|
68
|
+
import { elegirCarpetaEnMaquina, haySelectorDeCarpeta } from "../../agent/config/selectorEnMaquina.js";
|
|
67
69
|
import { cloudstudioDelProyecto } from "../../agent/config/configEnDisco.js";
|
|
68
70
|
import { abrirEnSistema } from "../../agent/cloudstudio/cloudstudioMcp.js";
|
|
69
71
|
import { nombreDePersona } from "../../agent/config/persona.js";
|
|
@@ -73,7 +75,7 @@ import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
|
|
|
73
75
|
import { RUTA_ARTEFACTOS, esRutaDeArtefacto } from "../../core/artefactos.js";
|
|
74
76
|
import { arbolDeProyecto, leerFicheroDeProyecto, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
75
77
|
import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/grafo/artefactosEnDisco.js";
|
|
76
|
-
import { CatalogoModelos } from "../../agent/config/catalogoModelos.js";
|
|
78
|
+
import { CatalogoModelos, baseUrlDeOllama, baseUrlDeOllamaCloud, capacidadesDeOllama, } from "../../agent/config/catalogoModelos.js";
|
|
77
79
|
import { Modelos } from "../../agent/config/modelos.js";
|
|
78
80
|
import { crearJuezDeTarea, invocarConModelos } from "../../agent/tareas/juezDeTarea.js";
|
|
79
81
|
import { SIN_CONSUMO } from "../../agent/subagentes/consumoExterno.js";
|
|
@@ -85,13 +87,14 @@ import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareas/tareasEnDis
|
|
|
85
87
|
import { nombreDeAdjuntoAceptable } from "../../core/adjuntos.js";
|
|
86
88
|
import { rutaMemoriaDeProyecto } from "../../agent/grafo/memoriaDeProyecto.js";
|
|
87
89
|
import { crearAumentador, invocarParaAumentar } from "../../agent/tareas/aumentador.js";
|
|
88
|
-
import { baseDeWorkspacePorOmision, conexionDeVestibulo, crearVestibulo, escribirProyectoEnDisco, esProyectoEnDisco, } from "./vestibulo.js";
|
|
90
|
+
import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo, crearVestibulo, escribirProyectoEnDisco, esProyectoEnDisco, } from "./vestibulo.js";
|
|
89
91
|
// Valor y no tipo: la traducción de una `Tarea` a lo que viaja vive JUNTO al tipo que
|
|
90
92
|
// produce y no aquí. Estuvo en este cierre, y ahí se cayó `veredicto` sin que nada se
|
|
91
93
|
// pusiera rojo (F1 de la revisión final) — el patrón de fallo que este repo ya tiene
|
|
92
94
|
// documentado cinco veces: una composición de producción dentro de algo que los tests
|
|
93
95
|
// doblan.
|
|
94
96
|
import { filaDeTarea } from "./transporte.js";
|
|
97
|
+
import { esEsfuerzo, nivelesDeEsfuerzo } from "../../core/esfuerzo.js";
|
|
95
98
|
/** Las dos rutas del cable. El cliente las tiene escritas en `apps/web/src/conexion.ts`. */
|
|
96
99
|
/**
|
|
97
100
|
* Cuánto se espera al aviso de «lo que ya había sin commitear» antes de anunciar sin él.
|
|
@@ -486,6 +489,18 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
486
489
|
* recuerda —como fallo— hasta que alguien lo vuelva a pedir a propósito.
|
|
487
490
|
*/
|
|
488
491
|
const catalogos = new Map();
|
|
492
|
+
/**
|
|
493
|
+
* Lo que el servidor de Ollama ha dicho de cada modelo, por `proveedor/modelo`.
|
|
494
|
+
*
|
|
495
|
+
* Es una memoria y no una caché con plazo: se llena la primera vez que ese modelo está en
|
|
496
|
+
* vigor y se vuelve a emitir el estado cuando llega. Ollama es el único que necesita esto
|
|
497
|
+
* —los demás salen de la tabla, que es síncrona—, y lo necesita porque sus modelos los
|
|
498
|
+
* elige el usuario: no hay prefijo que diga si `ministral-3:3b` piensa, pero `/api/show`
|
|
499
|
+
* sí lo contesta.
|
|
500
|
+
*/
|
|
501
|
+
const capacidadesVivas = new Map();
|
|
502
|
+
/** Las que ya se están preguntando, para no lanzar una consulta por cliente conectado. */
|
|
503
|
+
const capacidadesEnVuelo = new Set();
|
|
489
504
|
/**
|
|
490
505
|
* El estado de modelos: qué está en vigor y qué se puede elegir.
|
|
491
506
|
*
|
|
@@ -493,7 +508,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
493
508
|
* proyecto abierto no hay sesión y por tanto no hay modelo que afirmar: el campo se va
|
|
494
509
|
* y el cliente pinta «Elige modelo» en vez de una fila muerta.
|
|
495
510
|
*/
|
|
496
|
-
const emitirModelos = () =>
|
|
511
|
+
const emitirModelos = () => {
|
|
512
|
+
emitir(mensajeDeModelos());
|
|
513
|
+
// Después de emitir, no antes: lo que ya se sabe se pinta ya, y lo que falta llega en
|
|
514
|
+
// una segunda emisión. Al revés, cada cambio de modelo dejaría la pastilla esperando
|
|
515
|
+
// una consulta de red para pintar lo que no depende de ella.
|
|
516
|
+
asegurarCapacidades();
|
|
517
|
+
};
|
|
497
518
|
/**
|
|
498
519
|
* Los subagentes en vigor, compuestos pero sin mandar.
|
|
499
520
|
*
|
|
@@ -516,6 +537,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
516
537
|
// Ausente = no la tiene. No se emite `false` para que el cliente no tenga que
|
|
517
538
|
// distinguir entre «no» y «no consta»: aquí son lo mismo y la ausencia lo dice.
|
|
518
539
|
...(a.ejecucion === true ? { ejecucion: true } : {}),
|
|
540
|
+
// Igual que arriba: ausente es «no hay ninguno fijado», que es un dato y no un hueco.
|
|
541
|
+
...(a.esfuerzo === undefined ? {} : { esfuerzo: a.esfuerzo }),
|
|
519
542
|
skills: a.skills,
|
|
520
543
|
instrucciones: a.instrucciones,
|
|
521
544
|
origen: a.origen,
|
|
@@ -781,10 +804,43 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
781
804
|
// pueda leer el config global. Sin el segundo, Ajustes enseñaría «sin elegir» sobre una
|
|
782
805
|
// máquina con un defecto escrito, justo en la pantalla donde se configura.
|
|
783
806
|
const porDefecto = opciones.modeloPorDefecto?.();
|
|
807
|
+
/**
|
|
808
|
+
* El esfuerzo del modelo EN VIGOR, que es lo único de lo que se puede afirmar algo.
|
|
809
|
+
*
|
|
810
|
+
* Sin sesión no hay modelo en vigor y no viaja: la pastilla no se pinta, igual que
|
|
811
|
+
* `actual` no se inventa. Los niveles salen de la tabla pura; para Ollama hacen falta
|
|
812
|
+
* las capacidades vivas, y mientras no hayan llegado tampoco se afirma nada — se
|
|
813
|
+
* piden aparte y el estado se vuelve a emitir cuando contesten.
|
|
814
|
+
*/
|
|
815
|
+
const niveles = trabajo === undefined
|
|
816
|
+
? undefined
|
|
817
|
+
: nivelesDeEsfuerzo(trabajo.proveedor, trabajo.modelo, capacidadesVivas.get(`${trabajo.proveedor}/${trabajo.modelo}`));
|
|
818
|
+
const elegido = abierto?.estadoDeSesion.esfuerzo;
|
|
784
819
|
return {
|
|
785
820
|
clase: "modelos",
|
|
786
821
|
...(trabajo === undefined ? {} : { actual: `${trabajo.proveedor}/${trabajo.modelo}` }),
|
|
787
822
|
...(porDefecto === undefined ? {} : { porDefecto }),
|
|
823
|
+
...(niveles === undefined
|
|
824
|
+
? {}
|
|
825
|
+
: {
|
|
826
|
+
esfuerzo: {
|
|
827
|
+
niveles: [...niveles],
|
|
828
|
+
// El nivel guardado solo viaja si ESTE modelo lo admite. Si no, la pastilla
|
|
829
|
+
// diría «xhigh» sobre un modelo que va a ignorarlo — el valor sigue en la
|
|
830
|
+
// sesión y vuelve solo al cambiar a un modelo que lo acepte.
|
|
831
|
+
...(elegido !== undefined && niveles.includes(elegido) ? { actual: elegido } : {}),
|
|
832
|
+
// La nota de Ollama, MEDIDA: su servidor valida los cuatro niveles, pero el
|
|
833
|
+
// efecto lo pone el template de cada modelo y no es monótono —en
|
|
834
|
+
// `glm-5.3-flash:cloud`, `high` mide menos razonamiento que `medium`—.
|
|
835
|
+
// Callarlo haría que la pastilla afirmara una escala que ahí no existe.
|
|
836
|
+
...(trabajo?.proveedor === "ollama" || trabajo?.proveedor === "ollama-cloud"
|
|
837
|
+
? {
|
|
838
|
+
nota: "En Ollama el efecto de cada nivel lo decide el modelo, no el servidor:"
|
|
839
|
+
+ " medido, en algunos «high» razona MENOS que «medium».",
|
|
840
|
+
}
|
|
841
|
+
: {}),
|
|
842
|
+
},
|
|
843
|
+
}),
|
|
788
844
|
proveedores: [...PROVEEDORES, ...idsPersonalizados()].map((p) => ({
|
|
789
845
|
id: p,
|
|
790
846
|
nombre: nombreDeProveedor(p, personalizados()),
|
|
@@ -845,6 +901,20 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
845
901
|
for (const cliente of destinatarios)
|
|
846
902
|
actos = destino.conectar(cliente);
|
|
847
903
|
const modelos = mensajeDeModelos();
|
|
904
|
+
/**
|
|
905
|
+
* Y se pregunta por lo que falte, que en esta rama hay que pedir A MANO.
|
|
906
|
+
*
|
|
907
|
+
* La ráfaga COMPONE el mensaje en vez de emitirlo —va solo al recién llegado—, así que
|
|
908
|
+
* no pasa por `emitirModelos` y sin esta línea el camino más normal de todos —abrir el
|
|
909
|
+
* navegador sobre un proyecto ya abierto— no preguntaba nunca: la pastilla de esfuerzo
|
|
910
|
+
* no aparecía con Ollama hasta que algo más provocara una emisión. Lo encontró el test
|
|
911
|
+
* del cable, que es exactamente el patrón de fallo de esta arquitectura.
|
|
912
|
+
*
|
|
913
|
+
* No se mete DENTRO de `mensajeDeModelos` porque eso es un compositor: un efecto de red
|
|
914
|
+
* escondido en la función que arma un mensaje es la clase de cosa que luego dispara
|
|
915
|
+
* desde sitios que nadie esperaba.
|
|
916
|
+
*/
|
|
917
|
+
asegurarCapacidades();
|
|
848
918
|
// Los subagentes van en la ráfaga por lo mismo que los modelos: la ventana de ajustes
|
|
849
919
|
// se puede abrir en cuanto conecta, y sin esto enseñaría una lista vacía hasta que algo
|
|
850
920
|
// los cambiara — que es indistinguible de «no tienes ninguno».
|
|
@@ -856,6 +926,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
856
926
|
// La cola de tareas, si esta ejecución las tiene: la misma regla que `agentes`, sin
|
|
857
927
|
// esto la pestaña de tareas se quedaría vacía hasta el primer cambio de la cola.
|
|
858
928
|
const tareas = mensajeDeTareas();
|
|
929
|
+
// El workspace, por lo mismo que los subagentes y las skills: Ajustes se puede abrir en
|
|
930
|
+
// cuanto conecta, y un campo en blanco se lee como «no hay ninguno puesto».
|
|
931
|
+
const workspace = mensajeDeWorkspace();
|
|
859
932
|
/**
|
|
860
933
|
* Lo consumido por la sesión que se va a pintar. Va en la ráfaga por lo mismo que los
|
|
861
934
|
* modelos: una pestaña que conecta a mitad de sesión no vio los cambios anteriores, y
|
|
@@ -873,6 +946,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
873
946
|
cliente(skills);
|
|
874
947
|
if (tareas !== undefined)
|
|
875
948
|
cliente(tareas);
|
|
949
|
+
if (workspace !== undefined)
|
|
950
|
+
cliente(workspace);
|
|
876
951
|
if (consumoDeLaSesion !== undefined) {
|
|
877
952
|
cliente({
|
|
878
953
|
clase: "consumo",
|
|
@@ -1011,6 +1086,21 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1011
1086
|
});
|
|
1012
1087
|
/** Los cuatro nombres conocidos y nada más: lo que llega por el cable no elige binario. */
|
|
1013
1088
|
const esNombreDeHerramienta = (v) => v === "adb" || v === "emulator" || v === "xcrun" || v === "devicectl";
|
|
1089
|
+
const mensajeDeWorkspace = () => {
|
|
1090
|
+
const ruta = opciones.workspace?.();
|
|
1091
|
+
if (ruta === undefined)
|
|
1092
|
+
return undefined;
|
|
1093
|
+
// `puedeElegir` va solo cuando es cierto: ausente ya significa que no, y un `false`
|
|
1094
|
+
// escrito solo daría dos formas de decir lo mismo.
|
|
1095
|
+
return opciones.elegirCarpeta === undefined
|
|
1096
|
+
? { clase: "workspace", ruta }
|
|
1097
|
+
: { clase: "workspace", ruta, puedeElegir: true };
|
|
1098
|
+
};
|
|
1099
|
+
const emitirWorkspace = () => {
|
|
1100
|
+
const m = mensajeDeWorkspace();
|
|
1101
|
+
if (m !== undefined)
|
|
1102
|
+
emitir(m);
|
|
1103
|
+
};
|
|
1014
1104
|
const mensajeDeTareas = () => {
|
|
1015
1105
|
if (opciones.colaDeTareas === undefined)
|
|
1016
1106
|
return undefined;
|
|
@@ -1450,7 +1540,28 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1450
1540
|
}
|
|
1451
1541
|
try {
|
|
1452
1542
|
const modelos = await opciones.catalogoDeModelos(id);
|
|
1453
|
-
|
|
1543
|
+
/**
|
|
1544
|
+
* Cada modelo viaja con los niveles de esfuerzo que ADMITE, y se calculan aquí.
|
|
1545
|
+
*
|
|
1546
|
+
* Aquí y no en el cliente porque la tabla vive en `core/esfuerzo.ts` y la frontera
|
|
1547
|
+
* prohíbe que `apps/web/` importe de `src/`. La alternativa era una copia DECLARADA
|
|
1548
|
+
* al otro lado —como la del slug o la del workspace—, y no compensa: aquéllas son una
|
|
1549
|
+
* regla de tres líneas, y ésta es una tabla por familia que además cambia cada vez que
|
|
1550
|
+
* alguien MIDE un modelo nuevo. Dos tablas es un sitio donde una queda vieja sin que
|
|
1551
|
+
* nada lo diga.
|
|
1552
|
+
*
|
|
1553
|
+
* Ausente = ese modelo no admite ninguno, y entonces el control no se pinta.
|
|
1554
|
+
*/
|
|
1555
|
+
catalogos.set(id, {
|
|
1556
|
+
modelos: modelos.map((m) => {
|
|
1557
|
+
const niveles = nivelesDeEsfuerzo(id, m.id);
|
|
1558
|
+
return {
|
|
1559
|
+
id: m.id,
|
|
1560
|
+
...(m.nombre === undefined ? {} : { nombre: m.nombre }),
|
|
1561
|
+
...(niveles === undefined ? {} : { esfuerzos: [...niveles] }),
|
|
1562
|
+
};
|
|
1563
|
+
}),
|
|
1564
|
+
});
|
|
1454
1565
|
}
|
|
1455
1566
|
catch (error) {
|
|
1456
1567
|
// `ErrorCatalogoModelos` es publicable por contrato: nunca lleva la clave ni el
|
|
@@ -1876,6 +1987,72 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1876
1987
|
* Si no hay escritor, se DICE que no se ha guardado en vez de callarlo: un ajuste que
|
|
1877
1988
|
* parece puesto y no lo está es peor que uno que falta.
|
|
1878
1989
|
*/
|
|
1990
|
+
/**
|
|
1991
|
+
* El esfuerzo de la sesión abierta. `nivel` ausente = quitarlo.
|
|
1992
|
+
*
|
|
1993
|
+
* Se encola `/esfuerzo`, que es el MISMO manejador que usa el terminal: la función se
|
|
1994
|
+
* comparte y la sintaxis no se exporta, igual que con `/modelo`. Y **no se guarda ningún
|
|
1995
|
+
* defecto**: a diferencia del modelo, esto es de la sesión y solo de la sesión — un
|
|
1996
|
+
* defecto persistido es otra decisión y no se toma por inercia.
|
|
1997
|
+
*
|
|
1998
|
+
* Sin proyecto abierto no hay sesión a la que aplicárselo, y se DICE en vez de callarlo:
|
|
1999
|
+
* la pastilla solo se pinta con sesión, así que llegar aquí sin ella es un cliente
|
|
2000
|
+
* desincronizado y merece una frase, no un silencio.
|
|
2001
|
+
*/
|
|
2002
|
+
const atenderEsfuerzo = (nivel) => {
|
|
2003
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2004
|
+
if (abierto === undefined) {
|
|
2005
|
+
informar("no hay ninguna sesión abierta a la que fijarle el esfuerzo");
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (nivel === undefined) {
|
|
2009
|
+
abierto.consola.encolar("/esfuerzo ninguno");
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
// Se criba contra el vocabulario ANTES de encolar: lo que llega del cable acaba siendo
|
|
2013
|
+
// un parámetro de una API, y el manejador ya lo rechazaría, pero su rechazo se
|
|
2014
|
+
// imprimiría como una línea de consola en vez de un aviso. Aquí se dice mejor.
|
|
2015
|
+
if (!esEsfuerzo(nivel)) {
|
|
2016
|
+
informar(`«${nivel}» no es un nivel de esfuerzo`);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
abierto.consola.encolar(`/esfuerzo ${nivel}`);
|
|
2020
|
+
};
|
|
2021
|
+
/**
|
|
2022
|
+
* Pregunta a Ollama si el modelo en vigor piensa, y vuelve a emitir cuando conteste.
|
|
2023
|
+
*
|
|
2024
|
+
* Una vez por modelo y por proceso: la respuesta se guarda en `capacidadesVivas`, y sin
|
|
2025
|
+
* el conjunto `capacidadesEnVuelo` cada cliente que se conecta lanzaría la suya. Mismo
|
|
2026
|
+
* molde que `catalogos`.
|
|
2027
|
+
*
|
|
2028
|
+
* Solo Ollama, porque solo Ollama lo necesita: los demás salen de la tabla, que es pura
|
|
2029
|
+
* y síncrona. Y su fallo se TRAGA — lo que no se pudo preguntar deja la pastilla sin
|
|
2030
|
+
* pintar, que es la dirección segura y ya es lo que pasaba antes de que esto existiera.
|
|
2031
|
+
*/
|
|
2032
|
+
const asegurarCapacidades = () => {
|
|
2033
|
+
if (opciones.capacidadesDeModelo === undefined)
|
|
2034
|
+
return;
|
|
2035
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2036
|
+
if (abierto === undefined)
|
|
2037
|
+
return;
|
|
2038
|
+
const trabajo = resolver(abierto.estadoDeSesion.fuentes).trabajo;
|
|
2039
|
+
if (trabajo.proveedor !== "ollama" && trabajo.proveedor !== "ollama-cloud")
|
|
2040
|
+
return;
|
|
2041
|
+
const clave = `${trabajo.proveedor}/${trabajo.modelo}`;
|
|
2042
|
+
if (capacidadesVivas.has(clave) || capacidadesEnVuelo.has(clave))
|
|
2043
|
+
return;
|
|
2044
|
+
capacidadesEnVuelo.add(clave);
|
|
2045
|
+
void opciones
|
|
2046
|
+
.capacidadesDeModelo(trabajo.proveedor, trabajo.modelo)
|
|
2047
|
+
.then((vivas) => {
|
|
2048
|
+
if (vivas !== undefined) {
|
|
2049
|
+
capacidadesVivas.set(clave, vivas);
|
|
2050
|
+
emitirModelos();
|
|
2051
|
+
}
|
|
2052
|
+
})
|
|
2053
|
+
.catch(() => { })
|
|
2054
|
+
.finally(() => capacidadesEnVuelo.delete(clave));
|
|
2055
|
+
};
|
|
1879
2056
|
const atenderModelo = (id) => {
|
|
1880
2057
|
try {
|
|
1881
2058
|
parsear(id);
|
|
@@ -3329,6 +3506,40 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3329
3506
|
respuesta.end();
|
|
3330
3507
|
return;
|
|
3331
3508
|
}
|
|
3509
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "workspace" && typeof mensaje.ruta === "string") {
|
|
3510
|
+
/**
|
|
3511
|
+
* La regla se aplica AQUÍ además de en la pantalla. El cliente lleva su copia
|
|
3512
|
+
* declarada para poder explicar el no —`informar` no llega al navegador desde el
|
|
3513
|
+
* vestíbulo—, pero la barrera vive en el servidor: una pantalla solo esconde un botón.
|
|
3514
|
+
*
|
|
3515
|
+
* Y se reemite SIEMPRE, se haya escrito o no: con el valor nuevo cuando valió y con el
|
|
3516
|
+
* de antes cuando no, así el campo acaba enseñando lo que hay en disco y no lo que se
|
|
3517
|
+
* tecleó. Un rechazo mudo dejaría la pantalla afirmando un ajuste que no está puesto.
|
|
3518
|
+
*/
|
|
3519
|
+
opciones.guardarWorkspace?.(mensaje.ruta);
|
|
3520
|
+
emitirWorkspace();
|
|
3521
|
+
respuesta.writeHead(204);
|
|
3522
|
+
respuesta.end();
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "elegirCarpeta") {
|
|
3526
|
+
/**
|
|
3527
|
+
* El diálogo lo abre el SISTEMA donde corre la consola, así que esto tarda lo que
|
|
3528
|
+
* tarde una persona. Se contesta 204 en el acto y la carpeta llega después por el SSE:
|
|
3529
|
+
* dejar la petición abierta minutos enteros es lo que este servidor no hace en ningún
|
|
3530
|
+
* otro sitio.
|
|
3531
|
+
*
|
|
3532
|
+
* Se emite SIEMPRE, también sin carpeta: el cliente encendió su «abriendo…» al pedirlo
|
|
3533
|
+
* y sin respuesta se quedaría encendido para siempre después de un simple «cancelar».
|
|
3534
|
+
*/
|
|
3535
|
+
void (async () => {
|
|
3536
|
+
const elegida = await opciones.elegirCarpeta?.(opciones.workspace?.());
|
|
3537
|
+
emitir(elegida === undefined ? { clase: "carpetaElegida" } : { clase: "carpetaElegida", ruta: elegida });
|
|
3538
|
+
})().catch(contar);
|
|
3539
|
+
respuesta.writeHead(204);
|
|
3540
|
+
respuesta.end();
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3332
3543
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "modelosDeMotor" && typeof mensaje.motor === "string") {
|
|
3333
3544
|
void atenderModelosDeMotor(mensaje.motor).catch(contar);
|
|
3334
3545
|
respuesta.writeHead(204);
|
|
@@ -3366,6 +3577,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3366
3577
|
respuesta.end();
|
|
3367
3578
|
return;
|
|
3368
3579
|
}
|
|
3580
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "esfuerzo") {
|
|
3581
|
+
atenderEsfuerzo(mensaje.nivel);
|
|
3582
|
+
respuesta.writeHead(204);
|
|
3583
|
+
respuesta.end();
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3369
3586
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "agente") {
|
|
3370
3587
|
atenderAgente(mensaje);
|
|
3371
3588
|
respuesta.writeHead(204);
|
|
@@ -3841,12 +4058,71 @@ export function augmentacionCableada(opciones) {
|
|
|
3841
4058
|
export function commitDeTurnoCableado(opciones) {
|
|
3842
4059
|
const commitear = opciones.commitear ?? commitDeTurno;
|
|
3843
4060
|
return async (raiz, mensaje, sesion) => {
|
|
3844
|
-
if (!dentroDelWorkspace(raiz, opciones.base))
|
|
4061
|
+
if (!dentroDelWorkspace(raiz, opciones.base()))
|
|
3845
4062
|
return undefined;
|
|
3846
4063
|
const hecho = await commitear(raiz, mensaje, sesion);
|
|
3847
4064
|
return hecho.via === "fallo" ? `no se pudo commitear el turno: ${hecho.motivo}` : undefined;
|
|
3848
4065
|
};
|
|
3849
4066
|
}
|
|
4067
|
+
/**
|
|
4068
|
+
* La mudanza de una vez de las copias que siguen en el reparto viejo, cableada — y extraída
|
|
4069
|
+
* por el motivo de siempre: compuesta dentro del cierre de `arrancarConsolaWeb`, que todos
|
|
4070
|
+
* sus tests doblan, la regla estaría escrita y no probada.
|
|
4071
|
+
*
|
|
4072
|
+
* Lo que aquí se puede caer sin que TypeScript diga nada son las DOS bases, que no son la
|
|
4073
|
+
* misma cuenta: la VIEJA es `settings.workspace` si estaba puesto —el reparto viejo metía su
|
|
4074
|
+
* `workspace/` en medio fuera cual fuera la base— o `~/.xonecode` a secas; la de AHORA es ese
|
|
4075
|
+
* mismo `settings.workspace` o `~/.xonecode/workspace`. Confundirlas en cualquiera de los dos
|
|
4076
|
+
* sentidos da el mismo resultado: una mudanza que no encuentra nada y no dice nada.
|
|
4077
|
+
*
|
|
4078
|
+
* Y se ESCRIBE en el terminal, nunca por el cable: lo que se cuenta son rutas de la máquina
|
|
4079
|
+
* y nombres de proyecto (`sinRutas`). Corre antes de que exista el vestíbulo, así que ni
|
|
4080
|
+
* siquiera hay cable donde emitirlo.
|
|
4081
|
+
*/
|
|
4082
|
+
export function mudarWorkspaceLegadoCableado(opciones) {
|
|
4083
|
+
const mudar = opciones.mudar ?? mudarWorkspaceLegado;
|
|
4084
|
+
return mudar({
|
|
4085
|
+
legado: opciones.settings.workspace ?? baseDeWorkspaceLegada(),
|
|
4086
|
+
workspace: opciones.settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
4087
|
+
entornos: opciones.settings.entornos.map((e) => e.id),
|
|
4088
|
+
escribir: opciones.escribir,
|
|
4089
|
+
});
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* Las dos mitades del ajuste del workspace, cableadas — y extraídas por el motivo de
|
|
4093
|
+
* siempre: dentro del cierre de `arrancarConsolaWeb`, que todos sus tests doblan, ni una ni
|
|
4094
|
+
* otra estaría probada. Y aquí `guardarWorkspace` LLEVABA la marca: existía en
|
|
4095
|
+
* `agent/config/settingsEnDisco.ts` con un test propio y CERO llamadores de producción, o
|
|
4096
|
+
* sea el ajuste escrito que no hacía nada porque nadie lo había enchufado.
|
|
4097
|
+
*
|
|
4098
|
+
* Tres cosas que se deciden aquí y no en la pantalla:
|
|
4099
|
+
* - **Qué se LEE**: el disco, en cada emisión, no un valor capturado al arrancar. Es la misma
|
|
4100
|
+
* regla que `baseDeWorkspace`, y aquí además cierra el lazo: lo que se acaba de guardar es
|
|
4101
|
+
* lo que se vuelve a emitir.
|
|
4102
|
+
* - **Qué se ACEPTA**: `motivoDeWorkspaceInaceptable` sobre la ruta ya expandida, en el
|
|
4103
|
+
* servidor. El cliente lleva su copia declarada para explicar el no, pero una pantalla
|
|
4104
|
+
* solo esconde un botón.
|
|
4105
|
+
* - **Qué se GUARDA y qué se ENSEÑA**: la ruta ENTERA, nunca un `~/…`. En el fichero porque
|
|
4106
|
+
* la casa cambia de una máquina a otra y un `~` dentro solo significa algo para quien lo
|
|
4107
|
+
* escribió; en la pantalla porque una ruta abreviada no se puede comprobar de un vistazo,
|
|
4108
|
+
* que es para lo que ese campo existe. Se probó a mandarla abreviada —para que el caso
|
|
4109
|
+
* normal no llevara el nombre de la cuenta— y no se sostiene: la cabecera de esa misma
|
|
4110
|
+
* consola ya saluda por el nombre del usuario. El `~` sigue ACEPTÁNDOSE al teclear, que es
|
|
4111
|
+
* comodidad de entrada y otra cosa.
|
|
4112
|
+
*/
|
|
4113
|
+
export function ajusteDeWorkspaceCableado(opciones) {
|
|
4114
|
+
const leer = opciones.leer ?? (() => cargarSettings().settings.workspace);
|
|
4115
|
+
const guardar = opciones.guardar ?? ((ruta) => void guardarWorkspaceEnDisco(undefined, ruta));
|
|
4116
|
+
return {
|
|
4117
|
+
workspace: () => leer() ?? baseDeWorkspacePorOmision(),
|
|
4118
|
+
guardarWorkspace: (ruta) => {
|
|
4119
|
+
const absoluta = expandirConCasa(ruta, opciones.casa);
|
|
4120
|
+
if (motivoDeWorkspaceInaceptable(absoluta) !== undefined)
|
|
4121
|
+
return;
|
|
4122
|
+
guardar(absoluta);
|
|
4123
|
+
},
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
3850
4126
|
export function construirCorredorDeTareasCableado(opciones) {
|
|
3851
4127
|
const disco = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
|
|
3852
4128
|
/**
|
|
@@ -4040,6 +4316,17 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4040
4316
|
if (offline && opciones.guion !== true) {
|
|
4041
4317
|
escribir("este directorio es un proyecto offline: ábrelo con «xonecode --cli»\n");
|
|
4042
4318
|
}
|
|
4319
|
+
/**
|
|
4320
|
+
* La mudanza del reparto viejo, ANTES de que exista el vestíbulo y de que arranque el
|
|
4321
|
+
* corredor de tareas. El orden no es estético: el vestíbulo compone raíces con el reparto
|
|
4322
|
+
* de AHORA y una tarea pendiente lleva su raíz grabada, así que cualquiera de los dos
|
|
4323
|
+
* corriendo antes abriría una carpeta que esta función está a punto de mover.
|
|
4324
|
+
*
|
|
4325
|
+
* Y va aquí y no en el vestíbulo porque no es del vestíbulo: es de la MÁQUINA, pasa una
|
|
4326
|
+
* sola vez, y cuando no quede nadie con el reparto viejo en disco se retira de un tirón
|
|
4327
|
+
* junto con `core/mudanzaDeWorkspace.ts`.
|
|
4328
|
+
*/
|
|
4329
|
+
mudarWorkspaceLegadoCableado({ settings: cargarSettings().settings, escribir });
|
|
4043
4330
|
const arrancar = opciones.crearServidor ?? arrancarServidor;
|
|
4044
4331
|
const servidor = await arrancar({
|
|
4045
4332
|
puerto: opciones.puerto,
|
|
@@ -4143,6 +4430,15 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4143
4430
|
// la raíz que la tarea necesita para poder correr. Ver el comentario de
|
|
4144
4431
|
// `OpcionesDeMontaje.colaDeTareas`.
|
|
4145
4432
|
...opcionesDeTareas,
|
|
4433
|
+
// Dónde se bajan las copias: leerlo y elegirlo. La composición está EXTRAÍDA y probada
|
|
4434
|
+
// (`ajusteDeWorkspaceCableado`) por el patrón de fallo de siempre — y aquí el escritor
|
|
4435
|
+
// ya venía con la marca puesta: existía con su test y sin un solo llamador.
|
|
4436
|
+
...ajusteDeWorkspaceCableado({ casa: homedir() }),
|
|
4437
|
+
// El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
|
|
4438
|
+
// monta y el botón no llega a existir.
|
|
4439
|
+
...(haySelectorDeCarpeta()
|
|
4440
|
+
? { elegirCarpeta: (desde) => elegirCarpetaEnMaquina(desde === undefined ? {} : { desde }) }
|
|
4441
|
+
: {}),
|
|
4146
4442
|
// Los dos puertos del selector de modelos, con las piezas reales: quién tiene
|
|
4147
4443
|
// credencial (`auth.json` o el entorno, leído desde el cwd) y el catálogo VIVO.
|
|
4148
4444
|
hayCredencial: (proveedor) => hayCredencial(proveedor, opciones.cwd),
|
|
@@ -4227,6 +4523,14 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4227
4523
|
const modelos = await new CatalogoModelos(undefined, undefined, proveedoresPersonalizados).listar(proveedor);
|
|
4228
4524
|
return modelos.map((m) => ({ id: m.id, ...(m.nombre === undefined ? {} : { nombre: m.nombre }) }));
|
|
4229
4525
|
},
|
|
4526
|
+
/**
|
|
4527
|
+
* Solo Ollama tiene a quién preguntarle esto, y por eso los demás devuelven «no consta»
|
|
4528
|
+
* aquí en vez de que lo decida `asegurarCapacidades`: quien sabe qué proveedores saben
|
|
4529
|
+
* contestar es este cableado, no el servidor.
|
|
4530
|
+
*/
|
|
4531
|
+
capacidadesDeModelo: async (proveedor, modelo) => proveedor === "ollama" || proveedor === "ollama-cloud"
|
|
4532
|
+
? capacidadesDeOllama(modelo, proveedor === "ollama" ? baseUrlDeOllama() : baseUrlDeOllamaCloud())
|
|
4533
|
+
: undefined,
|
|
4230
4534
|
// Se releen de disco en cada consulta, igual que los ajustes de dispositivos: esta
|
|
4231
4535
|
// misma ventana los da de alta, y una lista capturada al montar las rutas se quedaría
|
|
4232
4536
|
// vieja hasta reiniciar el proceso.
|
|
@@ -4393,14 +4697,25 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
|
4393
4697
|
// El commit del turno, con el sello de la sesión. La composición está EXTRAÍDA y probada
|
|
4394
4698
|
// (`commitDeTurnoCableado`): el argumento que se cae en una lambda escrita a mano es
|
|
4395
4699
|
// justo el que sostiene la atribución de Revisión.
|
|
4396
|
-
commitearTurno: commitDeTurnoCableado({
|
|
4700
|
+
commitearTurno: commitDeTurnoCableado({
|
|
4701
|
+
base: () => cargarSettings().settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
4702
|
+
}),
|
|
4397
4703
|
olvidarMarcaDeSesion: olvidarSesion,
|
|
4398
4704
|
// La memoria del agente por hilo. `historica` deja de ser «se reabrió» para ser «no hay
|
|
4399
4705
|
// checkpoint que cargar», y borrar una sesión se lleva también su checkpoint.
|
|
4400
4706
|
hayMemoriaDeHilo: async (raiz, hilo) => hayCheckpoint(crearCheckpointerDeProyecto(raiz), hilo),
|
|
4401
4707
|
olvidarMemoriaDeHilo: async (raiz, hilo) => olvidarHilo(crearCheckpointerDeProyecto(raiz), hilo),
|
|
4402
4708
|
entornos: settings.entornos,
|
|
4403
|
-
|
|
4709
|
+
/**
|
|
4710
|
+
* Dónde se bajan las copias, RELEÍDO en cada uso y no resuelto aquí.
|
|
4711
|
+
*
|
|
4712
|
+
* `settings.workspace` se cambia desde Ajustes con la consola en marcha, así que un
|
|
4713
|
+
* valor capturado al arrancar dejaría el resto del proceso bajando al sitio de antes
|
|
4714
|
+
* mientras la pantalla enseña el nuevo — el ajuste escrito que no hace nada. Es lo
|
|
4715
|
+
* mismo que ya hacen `sinAprobacion` y el tope de concurrencia, y por lo mismo: releer
|
|
4716
|
+
* un JSON pequeño no cuesta nada al lado de equivocarse de carpeta.
|
|
4717
|
+
*/
|
|
4718
|
+
baseDeWorkspace: () => cargarSettings().settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
4404
4719
|
// La URL de la web para que la página del callback devuelva AQUÍ y no diga «vuelve a
|
|
4405
4720
|
// la terminal», que en un navegador es falso.
|
|
4406
4721
|
...conexionDeVestibulo(servidor.url),
|