xonecode 0.11.0 → 0.12.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-BNCMVjWv.js → index-BCO9A9PF.js} +74 -74
- package/apps/web/dist/assets/index-CBbdNsze.css +1 -0
- package/apps/web/dist/assets/{php-DPbDT8rX.js → php-DOl4aWua.js} +1 -1
- package/apps/web/dist/assets/{ruby-Bfvws9R8.js → ruby-DFYuLGa1.js} +1 -1
- package/apps/web/dist/iconos/conectores/deepwiki.png +0 -0
- package/apps/web/dist/iconos/conectores/jira.png +0 -0
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/conectores/conectoresEnDisco.js +188 -0
- package/dist/agent/conectores/conectoresEnDisco.js.map +1 -0
- package/dist/agent/conectores/proveedorDeConector.js +56 -0
- package/dist/agent/conectores/proveedorDeConector.js.map +1 -0
- package/dist/agent/conectores/servicioDeConectores.js +496 -0
- package/dist/agent/conectores/servicioDeConectores.js.map +1 -0
- package/dist/agent/config/catalogoModelos.js +2 -0
- package/dist/agent/config/catalogoModelos.js.map +1 -1
- package/dist/agent/config/modelos.js +27 -0
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/core/conectores.js +191 -0
- package/dist/core/conectores.js.map +1 -0
- package/dist/core/modelos.js +23 -1
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/settings.js +45 -0
- package/dist/core/settings.js.map +1 -1
- package/dist/web/servidor/arranque.js +248 -1
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/servidor.js +16 -1
- package/dist/web/servidor/servidor.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +45 -7
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/index-BAyybSz-.css +0 -1
|
@@ -66,7 +66,7 @@ import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedore
|
|
|
66
66
|
import { crearCheckpointerDeProyecto, mantenimientoDelCheckpointer, } from "../../agent/sesiones/checkpointer.js";
|
|
67
67
|
import { resumenDePoda } from "../../core/podaDeCheckpoint.js";
|
|
68
68
|
import { cargarSettings, guardarConcurrenciaDeTareas, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, olvidarEntornoDeSettings, copiasDeEntorno, borrarCopiasDeEntorno, } from "../../agent/config/settingsEnDisco.js";
|
|
69
|
-
import { dentroDelWorkspace, expandirConCasa, motivoParaNoOlvidarEntorno, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
|
|
69
|
+
import { dentroDelWorkspace, expandirConCasa, motivoParaNoOlvidarEntorno, motivoDeNombreDeEntornoInaceptable, motivoDeWorkspaceInaceptable, } from "../../core/settings.js";
|
|
70
70
|
import { esModoDeEscritura, MODO_POR_OMISION, } from "../../core/modoDeEscritura.js";
|
|
71
71
|
import { mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
|
|
72
72
|
import { elegirCarpetaEnMaquina, haySelectorDeCarpeta } from "../../agent/config/selectorEnMaquina.js";
|
|
@@ -106,6 +106,8 @@ import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo,
|
|
|
106
106
|
// doblan.
|
|
107
107
|
import { filaDeTarea } from "./transporte.js";
|
|
108
108
|
import { esEsfuerzo, nivelesDeEsfuerzo } from "../../core/esfuerzo.js";
|
|
109
|
+
import { definicionDelCable, RUTA_CALLBACK_MCP } from "../../core/conectores.js";
|
|
110
|
+
import { servicioDeConectoresCableado } from "../../agent/conectores/servicioDeConectores.js";
|
|
109
111
|
/** Las dos rutas del cable. El cliente las tiene escritas en `apps/web/src/conexion.ts`. */
|
|
110
112
|
/**
|
|
111
113
|
* Cuánto se espera al aviso de «lo que ya había sin commitear» antes de anunciar sin él.
|
|
@@ -197,6 +199,14 @@ function tipoServido(mime) {
|
|
|
197
199
|
? `${mime}; charset=utf-8`
|
|
198
200
|
: mime;
|
|
199
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Las cinco entidades de HTML, para la página del callback OAuth de un conector. El
|
|
204
|
+
* `mensaje` que escapa es siempre una frase NUESTRA (`ServicioDeConectores.completar`), pero
|
|
205
|
+
* se escapa igual: defensa en profundidad, no confianza en el origen.
|
|
206
|
+
*/
|
|
207
|
+
function escaparHtml(texto) {
|
|
208
|
+
return texto.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
209
|
+
}
|
|
200
210
|
/**
|
|
201
211
|
* Monta `/eventos` y `/accion` sobre un servidor ya levantado.
|
|
202
212
|
*
|
|
@@ -966,6 +976,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
966
976
|
// El workspace, por lo mismo que los subagentes y las skills: Ajustes se puede abrir en
|
|
967
977
|
// cuanto conecta, y un campo en blanco se lee como «no hay ninguno puesto».
|
|
968
978
|
const workspace = mensajeDeWorkspace();
|
|
979
|
+
// Y los conectores MCP, tras el workspace y por la misma regla: solo si esta ejecución
|
|
980
|
+
// los monta, y entonces la ventana de Ajustes no se abre con la sección en blanco.
|
|
981
|
+
const conectores = mensajeDeConectores();
|
|
969
982
|
/**
|
|
970
983
|
* Lo consumido por la sesión que se va a pintar. Va en la ráfaga por lo mismo que los
|
|
971
984
|
* modelos: una pestaña que conecta a mitad de sesión no vio los cambios anteriores, y
|
|
@@ -985,6 +998,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
985
998
|
cliente(tareas);
|
|
986
999
|
if (workspace !== undefined)
|
|
987
1000
|
cliente(workspace);
|
|
1001
|
+
if (conectores !== undefined)
|
|
1002
|
+
cliente(conectores);
|
|
988
1003
|
if (consumoDeLaSesion !== undefined) {
|
|
989
1004
|
cliente({
|
|
990
1005
|
clase: "consumo",
|
|
@@ -1129,6 +1144,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1129
1144
|
});
|
|
1130
1145
|
/** Los cuatro nombres conocidos y nada más: lo que llega por el cable no elige binario. */
|
|
1131
1146
|
const esNombreDeHerramienta = (v) => v === "adb" || v === "emulator" || v === "xcrun" || v === "devicectl";
|
|
1147
|
+
/** Las seis acciones conocidas sobre UN conector, y nada más: lo demás es un 400. */
|
|
1148
|
+
const esAccionDeConector = (v) => v === "anadir" || v === "quitar" || v === "probar" || v === "autorizar" || v === "desconectar" || v === "crear";
|
|
1132
1149
|
const mensajeDeWorkspace = () => {
|
|
1133
1150
|
const ruta = opciones.workspace?.();
|
|
1134
1151
|
if (ruta === undefined)
|
|
@@ -1144,6 +1161,76 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1144
1161
|
if (m !== undefined)
|
|
1145
1162
|
emitir(m);
|
|
1146
1163
|
};
|
|
1164
|
+
/**
|
|
1165
|
+
* El servicio de conectores, construido UNA VEZ al montar — no en cada mensaje, porque es
|
|
1166
|
+
* quien guarda en memoria las pruebas y las autorizaciones pendientes (`servicioDeConectores.ts`).
|
|
1167
|
+
* `alCambiar` es lo que hace que un cambio que ocurre en segundo plano (autorizar, probar)
|
|
1168
|
+
* llegue por el cable sin que nadie lo pida — la misma costura que `emitirCambioDeTareas`.
|
|
1169
|
+
* Ausente la OPCIÓN = esta ejecución no monta conectores.
|
|
1170
|
+
*/
|
|
1171
|
+
const servicioConectores = opciones.conectores?.(() => emitirConectores());
|
|
1172
|
+
const mensajeDeConectores = () => {
|
|
1173
|
+
if (servicioConectores === undefined)
|
|
1174
|
+
return undefined;
|
|
1175
|
+
// El catálogo lo compone el SERVICIO y no se arma aquí: es `catálogo ∪ definiciones`, y una
|
|
1176
|
+
// segunda composición en este fichero solo traería las filas de código — dejando sin nombre
|
|
1177
|
+
// en pantalla a todo conector escrito a mano.
|
|
1178
|
+
const { catalogo, conectores, desconocidos, ilegible, error } = servicioConectores.lista();
|
|
1179
|
+
return {
|
|
1180
|
+
clase: "conectores",
|
|
1181
|
+
catalogo,
|
|
1182
|
+
conectores,
|
|
1183
|
+
desconocidos,
|
|
1184
|
+
...(ilegible ? { ilegible } : {}),
|
|
1185
|
+
...(error === undefined ? {} : { error }),
|
|
1186
|
+
};
|
|
1187
|
+
};
|
|
1188
|
+
const emitirConectores = () => {
|
|
1189
|
+
const m = mensajeDeConectores();
|
|
1190
|
+
if (m !== undefined)
|
|
1191
|
+
emitir(m);
|
|
1192
|
+
};
|
|
1193
|
+
/**
|
|
1194
|
+
* Autoriza un conector POR DONDE ÉL PIDA: OAuth abre el navegador del sistema y `api-key` pide
|
|
1195
|
+
* una clave por la consola. `ninguna` no se autoriza —por eso no llega a hacer nada— y un id
|
|
1196
|
+
* que no resuelve tampoco.
|
|
1197
|
+
*
|
|
1198
|
+
* Es UNA función porque la usan DOS sitios —el «Conectar» de la fila y el encadenado de
|
|
1199
|
+
* `crear`—: dos copias serían dos sitios donde el carril puede divergir del que dice la fila, y
|
|
1200
|
+
* el carril lo contesta el SERVIDOR (`autenticacionDe`), nunca el cliente, que manda la
|
|
1201
|
+
* intención («autoriza este id») y no por dónde. Así un conector escrito a mano con `api-key`
|
|
1202
|
+
* no puede acabar abriendo un navegador por una pestaña que se equivocó: `autorizar` lo
|
|
1203
|
+
* rechazaría, y esto ni lo intenta.
|
|
1204
|
+
*/
|
|
1205
|
+
const autorizarConector = async (id) => {
|
|
1206
|
+
if (servicioConectores === undefined)
|
|
1207
|
+
return;
|
|
1208
|
+
const autenticacion = servicioConectores.autenticacionDe(id);
|
|
1209
|
+
if (autenticacion === "oauth") {
|
|
1210
|
+
// El puerto REAL: con `--puerto 0` el sistema elige uno, y es el mismo que ya usa
|
|
1211
|
+
// `servidor.url` para la ráfaga inicial — nunca uno construido a mano.
|
|
1212
|
+
await servicioConectores.autorizar(id, `http://127.0.0.1:${servidor.puerto}${RUTA_CALLBACK_MCP}`);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (autenticacion !== "api-key")
|
|
1216
|
+
return;
|
|
1217
|
+
// La MISMA resolución de consola que `pedirCredencial`: la del proyecto si hay uno abierto y
|
|
1218
|
+
// la del vestíbulo si no. La clave vuelve por `{clase: "secreto"}`, el único mensaje del
|
|
1219
|
+
// cable que la lleva.
|
|
1220
|
+
const consola = vestibulo.proyectoAbierto()?.consola.consola ?? vestibulo.consola.consola;
|
|
1221
|
+
// La pregunta DICE que no hace falta el prefijo, y no es un adorno: la criba
|
|
1222
|
+
// (`motivoDeClaveInaceptable`) rechaza cualquier valor con espacios —o sea justo el
|
|
1223
|
+
// «Bearer abc» que suele ser lo que uno copia del servidor— y el `Bearer` lo pone
|
|
1224
|
+
// `credencialDe` al componer la cabecera. Sin decirlo, el rechazo llega sin explicación.
|
|
1225
|
+
const clave = (await consola.leerSecreto(`clave de ${id} (sin el «Bearer»): `)).trim();
|
|
1226
|
+
// Cadena vacía es la consola sin nadie al otro lado y también el Enter a secas: en los dos
|
|
1227
|
+
// casos no se guarda nada y no se dice nada más — quien canceló no necesita un sermón.
|
|
1228
|
+
if (clave === "")
|
|
1229
|
+
return;
|
|
1230
|
+
// Una clave que no pasa la criba deja su motivo en `error`, que ya viaja por el cable: aquí
|
|
1231
|
+
// no hay nada que informar por consola, que es donde `pedirCredencial` sí lo hace.
|
|
1232
|
+
servicioConectores.guardarClave(id, clave);
|
|
1233
|
+
};
|
|
1147
1234
|
const mensajeDeTareas = () => {
|
|
1148
1235
|
if (opciones.colaDeTareas === undefined)
|
|
1149
1236
|
return undefined;
|
|
@@ -3525,6 +3612,45 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3525
3612
|
});
|
|
3526
3613
|
respuesta.end(datos);
|
|
3527
3614
|
});
|
|
3615
|
+
/**
|
|
3616
|
+
* `GET RUTA_CALLBACK_MCP` — a donde vuelve el navegador tras autorizar un conector.
|
|
3617
|
+
*
|
|
3618
|
+
* **Ruta PÚBLICA**, registrada con `registrarRutaPublica`: la cookie de sesión es
|
|
3619
|
+
* `SameSite=Strict`, así que la redirección que manda el proveedor OAuth llega SIN ella —
|
|
3620
|
+
* `Host` y `Origin` siguen delante, y la autenticación real es el `state` de un solo uso
|
|
3621
|
+
* que `completar` consume (`core/conectores.ts#interpretarCallback`).
|
|
3622
|
+
*
|
|
3623
|
+
* Solo se registra si esta ejecución monta conectores (`servicioConectores` viene de la
|
|
3624
|
+
* opción `conectores`): sin servicio no hay callback que atender.
|
|
3625
|
+
*
|
|
3626
|
+
* La página es MÍNIMA y NUESTRA — nunca repite la query cruda, que la escribe quien
|
|
3627
|
+
* redirige — y va con `Content-Security-Policy: default-src 'none'; style-src
|
|
3628
|
+
* 'unsafe-inline'` (sin red, sin script) y `Cache-Control: no-store` (no es un documento
|
|
3629
|
+
* que se pueda volver a servir de caché: el `state` ya se consumió).
|
|
3630
|
+
*/
|
|
3631
|
+
if (servicioConectores !== undefined) {
|
|
3632
|
+
servidor.registrarRutaPublica("GET", RUTA_CALLBACK_MCP, async (peticion, respuesta) => {
|
|
3633
|
+
const query = new URLSearchParams((peticion.url ?? "").split("?")[1] ?? "");
|
|
3634
|
+
// `completar` no debería lanzar —sus propios fallos vuelven como `{ok:false}`—, pero
|
|
3635
|
+
// esto es una ruta PÚBLICA sin cookie por delante: un reventón aquí no puede dejar al
|
|
3636
|
+
// navegador con una petición colgada ni con un 500 sin nada que decir. Y tampoco puede
|
|
3637
|
+
// desaparecer en silencio: `contar` es el mismo hueco por el que se avisa de cualquier
|
|
3638
|
+
// otro fallo suelto de este cable (`probar`/`autorizar`, más abajo).
|
|
3639
|
+
const { mensaje } = await servicioConectores.completar(query).catch((error) => {
|
|
3640
|
+
contar(error);
|
|
3641
|
+
return { ok: false, mensaje: "no se pudo completar la autorización: vuelve a pulsar Conectar" };
|
|
3642
|
+
});
|
|
3643
|
+
const cuerpo = `<!doctype html><html lang="es"><head><meta charset="utf-8">` +
|
|
3644
|
+
`<title>xonecode</title></head><body><p>${escaparHtml(mensaje)}</p>` +
|
|
3645
|
+
`<p>Ya puedes cerrar esta pestaña.</p></body></html>`;
|
|
3646
|
+
respuesta.writeHead(200, {
|
|
3647
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
3648
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'",
|
|
3649
|
+
"Cache-Control": "no-store",
|
|
3650
|
+
});
|
|
3651
|
+
respuesta.end(cuerpo);
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3528
3654
|
/**
|
|
3529
3655
|
* `POST /adjunto?tarea=<id>&nombre=<fichero>` — los BYTES de un adjunto de tarea.
|
|
3530
3656
|
*
|
|
@@ -3733,6 +3859,31 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3733
3859
|
respuesta.end();
|
|
3734
3860
|
return;
|
|
3735
3861
|
}
|
|
3862
|
+
if (typeof mensaje === "object" &&
|
|
3863
|
+
mensaje !== null &&
|
|
3864
|
+
mensaje.clase === "entorno" &&
|
|
3865
|
+
mensaje.accion === "renombrar") {
|
|
3866
|
+
// Como `olvidar`: la negativa se decide AQUÍ y antes del 204, para que el motivo pueda
|
|
3867
|
+
// ir en la respuesta — `informar` no llega al navegador desde el vestíbulo. Y el tipo
|
|
3868
|
+
// se mira antes de la regla porque la regla es de cadenas: un `nombre` que no sea texto
|
|
3869
|
+
// la haría reventar dentro del manejador, que es la única forma de que esta guarda no
|
|
3870
|
+
// conteste nada.
|
|
3871
|
+
const motivo = typeof mensaje.nombre === "string"
|
|
3872
|
+
? motivoDeNombreDeEntornoInaceptable(mensaje.nombre)
|
|
3873
|
+
: "el nombre tiene que ser texto";
|
|
3874
|
+
if (motivo !== undefined) {
|
|
3875
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
3876
|
+
respuesta.end(JSON.stringify({ motivo }));
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
void vestibulo
|
|
3880
|
+
.renombrarEntorno(mensaje.entorno, mensaje.nombre)
|
|
3881
|
+
.catch(contar)
|
|
3882
|
+
.finally(() => void anunciarAlta().catch(contar));
|
|
3883
|
+
respuesta.writeHead(204);
|
|
3884
|
+
respuesta.end();
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3736
3887
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "revision") {
|
|
3737
3888
|
void atenderRevision(mensaje.ruta).catch(contar);
|
|
3738
3889
|
respuesta.writeHead(204);
|
|
@@ -3912,6 +4063,84 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3912
4063
|
respuesta.end();
|
|
3913
4064
|
return;
|
|
3914
4065
|
}
|
|
4066
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "conector") {
|
|
4067
|
+
// Sin `as`: `mensaje.clase === "conector"` ya narrows a la forma de `MensajeDelCliente`
|
|
4068
|
+
// —la misma unión discriminada que `workspace` usa un renglón más arriba—, así que
|
|
4069
|
+
// `mensaje.accion` se lee directo. El `typeof` que sigue no es para el tipo (ese ya lo
|
|
4070
|
+
// sabe TypeScript): es la guarda de verdad contra un JSON malformado, que puede mentir
|
|
4071
|
+
// sobre lo que el tipo promete.
|
|
4072
|
+
if (servicioConectores === undefined ||
|
|
4073
|
+
typeof mensaje.accion !== "string" ||
|
|
4074
|
+
!esAccionDeConector(mensaje.accion)) {
|
|
4075
|
+
respuesta.writeHead(400);
|
|
4076
|
+
respuesta.end();
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* `crear` va ANTES y se lleva su propia guarda: es la única acción que NO trae `id` —lo
|
|
4081
|
+
* DERIVA el servidor del nombre, que es lo que impide que el cliente elija el nombre de un
|
|
4082
|
+
* fichero— y la única cuyo cuerpo hay que comprobar por FORMA antes de mirarlo de verdad.
|
|
4083
|
+
*/
|
|
4084
|
+
if (mensaje.accion === "crear") {
|
|
4085
|
+
const def = definicionDelCable(mensaje.definicion);
|
|
4086
|
+
// Un cuerpo sin la forma no es una definición inaceptable —esa se RECHAZA con su motivo,
|
|
4087
|
+
// y el motivo viaja en `error`—: es un mensaje malformado, y por eso es 400 y no 204.
|
|
4088
|
+
if (def === undefined) {
|
|
4089
|
+
respuesta.writeHead(400);
|
|
4090
|
+
respuesta.end();
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
// El servicio valida lo que tiene que valer (nombre, URL, slug libre) y su motivo sale en
|
|
4094
|
+
// `error`, que ya viaja.
|
|
4095
|
+
const id = servicioConectores.crear(def);
|
|
4096
|
+
// El encadenado es del SERVIDOR y va DESPUÉS de la escritura: pedir una clave —o abrir el
|
|
4097
|
+
// navegador— por un conector que no llegó a estar añadido no llevaría a ninguna parte. Y
|
|
4098
|
+
// sin `await`: esta respuesta sale en el acto y el resultado llega por `alCambiar`, igual
|
|
4099
|
+
// que en `probar` — con `await`, este POST se quedaría colgado hasta que alguien teclee.
|
|
4100
|
+
if (id !== undefined && def.autenticacion !== "ninguna")
|
|
4101
|
+
void autorizarConector(id).catch(contar);
|
|
4102
|
+
respuesta.writeHead(204);
|
|
4103
|
+
respuesta.end();
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
// Las otras CINCO sí traen id, y ahí el `typeof` es la guarda contra el JSON que miente.
|
|
4107
|
+
if (typeof mensaje.id !== "string") {
|
|
4108
|
+
respuesta.writeHead(400);
|
|
4109
|
+
respuesta.end();
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
const { id, accion } = mensaje;
|
|
4113
|
+
/**
|
|
4114
|
+
* Las cinco contestan 204 EN EL ACTO. `anadir`/`quitar`/`desconectar` son síncronas —su
|
|
4115
|
+
* `cambio()` ya reemite `conectores` antes de que esta función devuelva—; `probar` y
|
|
4116
|
+
* `autorizar` corren en segundo plano (red, o esperar al navegador que abre el sistema)
|
|
4117
|
+
* con `.catch(contar)`, la misma disciplina que `conexion` y `dispositivos`. El
|
|
4118
|
+
* resultado de las cinco llega por `emitir(mensajeDeConectores())` vía `alCambiar`,
|
|
4119
|
+
* nunca en esta respuesta.
|
|
4120
|
+
*/
|
|
4121
|
+
switch (accion) {
|
|
4122
|
+
case "anadir":
|
|
4123
|
+
servicioConectores.anadir(id);
|
|
4124
|
+
break;
|
|
4125
|
+
case "quitar":
|
|
4126
|
+
servicioConectores.quitar(id);
|
|
4127
|
+
break;
|
|
4128
|
+
case "desconectar":
|
|
4129
|
+
servicioConectores.desconectar(id);
|
|
4130
|
+
break;
|
|
4131
|
+
case "probar":
|
|
4132
|
+
void servicioConectores.probar(id).catch(contar);
|
|
4133
|
+
break;
|
|
4134
|
+
case "autorizar":
|
|
4135
|
+
// Por dónde se autoriza lo decide el SERVIDOR —OAuth abre el navegador, `api-key` pide
|
|
4136
|
+
// la clave—, así que este `case` no sabe ni de carriles ni de puertos.
|
|
4137
|
+
void autorizarConector(id).catch(contar);
|
|
4138
|
+
break;
|
|
4139
|
+
}
|
|
4140
|
+
respuesta.writeHead(204);
|
|
4141
|
+
respuesta.end();
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
3915
4144
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "modelosDeMotor" && typeof mensaje.motor === "string") {
|
|
3916
4145
|
void atenderModelosDeMotor(mensaje.motor).catch(contar);
|
|
3917
4146
|
respuesta.writeHead(204);
|
|
@@ -4542,6 +4771,21 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
4542
4771
|
},
|
|
4543
4772
|
};
|
|
4544
4773
|
}
|
|
4774
|
+
/**
|
|
4775
|
+
* La opción `conectores` de `montarRutas`, cableada — extraída por el MISMO motivo que
|
|
4776
|
+
* `ajusteDeWorkspaceCableado`: un literal inline dentro de `arrancarConsolaWeb` no quedaría
|
|
4777
|
+
* probado, y esa composición concreta —que la fábrica de verdad (`servicioDeConectoresCableado`)
|
|
4778
|
+
* llega montada, y con la CASA correcta— es exactamente el patrón de fallo que este repo ya
|
|
4779
|
+
* tiene documentado nueve veces.
|
|
4780
|
+
*
|
|
4781
|
+
* Es una función de una línea, pero la línea es la que puede caerse: sin este sitio propio,
|
|
4782
|
+
* `arrancarConsolaWeb` seguiría compilando y pasando sus tests aunque alguien borrara la
|
|
4783
|
+
* opción del objeto que le pasa a `montarRutas` — el mismo campo OPCIONAL que hace que
|
|
4784
|
+
* `ConsolaDeProyecto.consumo` fuera la novena vez.
|
|
4785
|
+
*/
|
|
4786
|
+
export function ajusteDeConectoresCableado(opciones) {
|
|
4787
|
+
return { conectores: (alCambiar) => servicioDeConectoresCableado({ casa: opciones.casa, alCambiar }) };
|
|
4788
|
+
}
|
|
4545
4789
|
export function construirCorredorDeTareasCableado(opciones) {
|
|
4546
4790
|
const disco = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
|
|
4547
4791
|
/**
|
|
@@ -4856,6 +5100,9 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4856
5100
|
// (`ajusteDeWorkspaceCableado`) por el patrón de fallo de siempre — y aquí el escritor
|
|
4857
5101
|
// ya venía con la marca puesta: existía con su test y sin un solo llamador.
|
|
4858
5102
|
...ajusteDeWorkspaceCableado({ casa: homedir() }),
|
|
5103
|
+
// Los conectores MCP, con la MISMA disciplina: la composición extraída y probada
|
|
5104
|
+
// (`ajusteDeConectoresCableado`), nunca un literal aquí dentro.
|
|
5105
|
+
...ajusteDeConectoresCableado({ casa: homedir() }),
|
|
4859
5106
|
// El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
|
|
4860
5107
|
// monta y el botón no llega a existir.
|
|
4861
5108
|
...(haySelectorDeCarpeta()
|