xonecode 0.9.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/THIRD_PARTY_NOTICES.md +56 -0
- package/apps/web/dist/assets/index-BCO9A9PF.js +485 -0
- package/apps/web/dist/assets/index-CBbdNsze.css +1 -0
- package/apps/web/dist/assets/{php-DjRQ1K-w.js → php-DOl4aWua.js} +1 -1
- package/apps/web/dist/assets/{ruby-BcLyZ3qi.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/apps/web/dist/openui/visor.css +1 -0
- package/apps/web/dist/openui/visor.js +9113 -0
- 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/agent/exportarPdf.js +5 -1
- package/dist/agent/exportarPdf.js.map +1 -1
- package/dist/agent/grafo/arbolDeProyecto.js +66 -1
- package/dist/agent/grafo/arbolDeProyecto.js.map +1 -1
- package/dist/agent/grafo/memoriaDeProyecto.js +3 -2
- package/dist/agent/grafo/memoriaDeProyecto.js.map +1 -1
- package/dist/agent/grafo/navegacionXone.js +8 -3
- package/dist/agent/grafo/navegacionXone.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +31 -15
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/grafo/unirSecciones.js +128 -0
- package/dist/agent/grafo/unirSecciones.js.map +1 -0
- package/dist/agent/grafo/xoneAgent.js +7 -0
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/motores/trueforge/capacidades.js +42 -1
- package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
- package/dist/agent/motores/trueforge/eventosTrueforge.js +52 -7
- package/dist/agent/motores/trueforge/eventosTrueforge.js.map +1 -1
- package/dist/agent/motores/trueforge/modeloLangchain.js +5 -2
- package/dist/agent/motores/trueforge/modeloLangchain.js.map +1 -1
- package/dist/agent/motores/trueforge/sesionTrueforge.js +19 -2
- package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
- package/dist/agent/motores/trueforge/toolsDeFichero.js +3 -2
- package/dist/agent/motores/trueforge/toolsDeFichero.js.map +1 -1
- package/dist/agent/motores/trueforge/trueforge.js +1 -1
- package/dist/agent/motores/trueforge/trueforge.js.map +1 -1
- package/dist/agent/navegacion/indiceEnDisco.js +10 -1
- package/dist/agent/navegacion/indiceEnDisco.js.map +1 -1
- package/dist/agent/planesEnDisco.js +64 -0
- package/dist/agent/planesEnDisco.js.map +1 -0
- package/dist/agent/sesiones/modeloDelCambio.js +70 -0
- package/dist/agent/sesiones/modeloDelCambio.js.map +1 -0
- package/dist/agent/sesiones/sesionGit.js +14 -0
- package/dist/agent/sesiones/sesionGit.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +95 -5
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +8 -2
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/subagenteCodex.js +1 -1
- package/dist/agent/subagentes/subagenteCodex.js.map +1 -1
- package/dist/agent/subagentes/subagenteExterno.js +1 -1
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/subagentes/subagenteOpencode.js +1 -1
- package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
- package/dist/agent/turno/puente.js +17 -1
- package/dist/agent/turno/puente.js.map +1 -1
- package/dist/agent/turno/resumenDeContexto.js +17 -4
- package/dist/agent/turno/resumenDeContexto.js.map +1 -1
- package/dist/agent/turno/turnoReal.js +16 -2
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/tui/transcript.js +4 -0
- package/dist/cli/tui/transcript.js.map +1 -1
- package/dist/core/actos.js.map +1 -1
- package/dist/core/agentes.js +17 -0
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/artefactos.js +22 -0
- package/dist/core/artefactos.js.map +1 -1
- package/dist/core/conectores.js +191 -0
- package/dist/core/conectores.js.map +1 -0
- package/dist/core/diffDeColecciones.js +84 -0
- package/dist/core/diffDeColecciones.js.map +1 -0
- package/dist/core/exportacion.js +6 -1
- package/dist/core/exportacion.js.map +1 -1
- package/dist/core/fotoDeColecciones.js +48 -0
- package/dist/core/fotoDeColecciones.js.map +1 -0
- package/dist/core/imagenesDeDocumento.js +88 -0
- package/dist/core/imagenesDeDocumento.js.map +1 -0
- package/dist/core/memoria.js +24 -0
- package/dist/core/memoria.js.map +1 -0
- package/dist/core/modelos.js +23 -1
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/navegacion.js.map +1 -1
- package/dist/core/notify.js +23 -5
- package/dist/core/notify.js.map +1 -1
- package/dist/core/ports.js.map +1 -1
- package/dist/core/serieDeEscrituras.js +13 -0
- package/dist/core/serieDeEscrituras.js.map +1 -1
- package/dist/core/settings.js +62 -1
- package/dist/core/settings.js.map +1 -1
- package/dist/core/tareasDelPlan.js +103 -0
- package/dist/core/tareasDelPlan.js.map +1 -0
- package/dist/core/trazaDeErrores.js +42 -9
- package/dist/core/trazaDeErrores.js.map +1 -1
- package/dist/core/turno.js +34 -4
- package/dist/core/turno.js.map +1 -1
- package/dist/web/servidor/arranque.js +445 -2
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/pielWeb.js +63 -4
- package/dist/web/servidor/pielWeb.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/dist/web/servidor/visorOpenui.js +63 -0
- package/dist/web/servidor/visorOpenui.js.map +1 -0
- package/package.json +1 -1
- package/skills/openui-builder/SKILL.md +48 -0
- package/apps/web/dist/assets/index-CtfT1BjI.css +0 -1
- package/apps/web/dist/assets/index-DqcZ7_Rx.js +0 -478
|
@@ -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";
|
|
@@ -77,7 +77,14 @@ import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "
|
|
|
77
77
|
import { commitDeTurno, cambiosPendientes, trabajoSinCommitear } from "../../agent/sesiones/gitSync.js";
|
|
78
78
|
import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
|
|
79
79
|
import { RUTA_ARTEFACTOS, esRutaDeArtefacto } from "../../core/artefactos.js";
|
|
80
|
-
import { arbolDeProyecto, leerFicheroDeProyecto, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
80
|
+
import { arbolDeProyecto, leerFicheroDeProyecto, mimeDeImagen, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
81
|
+
import { modeloEnDisco } from "../../agent/navegacion/indiceEnDisco.js";
|
|
82
|
+
import { ficherosDelProyecto } from "../../agent/turno/ficherosDelProyecto.js";
|
|
83
|
+
import { fotoDeColecciones } from "../../core/fotoDeColecciones.js";
|
|
84
|
+
import { modeloDelCambio } from "../../agent/sesiones/modeloDelCambio.js";
|
|
85
|
+
import { planesDelProyecto } from "../../agent/planesEnDisco.js";
|
|
86
|
+
import { CSP_DE_OPENUI, documentoDeOpenui, esArtefactoOpenui } from "./visorOpenui.js";
|
|
87
|
+
import { RUTA_IMAGEN_DEL_PROYECTO } from "../../core/imagenesDeDocumento.js";
|
|
81
88
|
import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/grafo/artefactosEnDisco.js";
|
|
82
89
|
import { CatalogoModelos, baseUrlDeOllama, baseUrlDeOllamaCloud, capacidadesDeOllama, } from "../../agent/config/catalogoModelos.js";
|
|
83
90
|
import { Modelos } from "../../agent/config/modelos.js";
|
|
@@ -99,6 +106,8 @@ import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo,
|
|
|
99
106
|
// doblan.
|
|
100
107
|
import { filaDeTarea } from "./transporte.js";
|
|
101
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";
|
|
102
111
|
/** Las dos rutas del cable. El cliente las tiene escritas en `apps/web/src/conexion.ts`. */
|
|
103
112
|
/**
|
|
104
113
|
* Cuánto se espera al aviso de «lo que ya había sin commitear» antes de anunciar sin él.
|
|
@@ -163,6 +172,21 @@ export const MS_ENTRE_PROGRESOS = 250;
|
|
|
163
172
|
export function raizDelClientePorOmision() {
|
|
164
173
|
return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "apps", "web", "dist");
|
|
165
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* El visor de OpenUI del build del cliente, o `undefined` si no está. Se lee en cada petición, como
|
|
177
|
+
* el resto del cliente: reconstruirlo se ve recargando, sin reiniciar el servidor.
|
|
178
|
+
*/
|
|
179
|
+
export function leerVisorOpenui(raizDelCliente) {
|
|
180
|
+
try {
|
|
181
|
+
return {
|
|
182
|
+
js: readFileSync(join(raizDelCliente, "openui", "visor.js"), "utf8"),
|
|
183
|
+
css: readFileSync(join(raizDelCliente, "openui", "visor.css"), "utf8"),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
166
190
|
export const FALTA_EL_BUILD = "falta el build del cliente: ejecuta «npm run build:web»";
|
|
167
191
|
/** Tope del cuerpo de `POST /accion`. Generoso para una prosa larga, finito porque el
|
|
168
192
|
* cuerpo se acumula en memoria y un cliente roto no puede llenarla. */
|
|
@@ -175,6 +199,14 @@ function tipoServido(mime) {
|
|
|
175
199
|
? `${mime}; charset=utf-8`
|
|
176
200
|
: mime;
|
|
177
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
|
+
}
|
|
178
210
|
/**
|
|
179
211
|
* Monta `/eventos` y `/accion` sobre un servidor ya levantado.
|
|
180
212
|
*
|
|
@@ -944,6 +976,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
944
976
|
// El workspace, por lo mismo que los subagentes y las skills: Ajustes se puede abrir en
|
|
945
977
|
// cuanto conecta, y un campo en blanco se lee como «no hay ninguno puesto».
|
|
946
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();
|
|
947
982
|
/**
|
|
948
983
|
* Lo consumido por la sesión que se va a pintar. Va en la ráfaga por lo mismo que los
|
|
949
984
|
* modelos: una pestaña que conecta a mitad de sesión no vio los cambios anteriores, y
|
|
@@ -963,6 +998,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
963
998
|
cliente(tareas);
|
|
964
999
|
if (workspace !== undefined)
|
|
965
1000
|
cliente(workspace);
|
|
1001
|
+
if (conectores !== undefined)
|
|
1002
|
+
cliente(conectores);
|
|
966
1003
|
if (consumoDeLaSesion !== undefined) {
|
|
967
1004
|
cliente({
|
|
968
1005
|
clase: "consumo",
|
|
@@ -1107,6 +1144,8 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1107
1144
|
});
|
|
1108
1145
|
/** Los cuatro nombres conocidos y nada más: lo que llega por el cable no elige binario. */
|
|
1109
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";
|
|
1110
1149
|
const mensajeDeWorkspace = () => {
|
|
1111
1150
|
const ruta = opciones.workspace?.();
|
|
1112
1151
|
if (ruta === undefined)
|
|
@@ -1122,6 +1161,76 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1122
1161
|
if (m !== undefined)
|
|
1123
1162
|
emitir(m);
|
|
1124
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
|
+
};
|
|
1125
1234
|
const mensajeDeTareas = () => {
|
|
1126
1235
|
if (opciones.colaDeTareas === undefined)
|
|
1127
1236
|
return undefined;
|
|
@@ -2744,6 +2853,73 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2744
2853
|
emitir({ clase: "arbol", rutas: [], recortado: false, error: "no se pudo listar el proyecto" });
|
|
2745
2854
|
}
|
|
2746
2855
|
};
|
|
2856
|
+
/**
|
|
2857
|
+
* El diff semántico de un `.xne` de Revisión. Siempre CONTESTA —la fila abierta espera, y un
|
|
2858
|
+
* silencio la dejaría cargando para siempre—, y la ruta pasa por la MISMA barrera que la del
|
|
2859
|
+
* lector de Ficheros antes de llegar a git.
|
|
2860
|
+
*/
|
|
2861
|
+
const atenderModeloDelCambio = async (ruta) => {
|
|
2862
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2863
|
+
const sesion = abierto?.sesion;
|
|
2864
|
+
const sinNada = (error) => emitir({ clase: "modeloDelCambio", ruta, error });
|
|
2865
|
+
if (abierto === undefined || opciones.modeloDelCambio === undefined)
|
|
2866
|
+
return sinNada("esta ejecución no sabe comparar el modelo");
|
|
2867
|
+
if (sesion === undefined)
|
|
2868
|
+
return sinNada("la sesión todavía no ha empezado");
|
|
2869
|
+
if (motivoDeRutaInaceptable(ruta) !== undefined)
|
|
2870
|
+
return sinNada("esa ruta no se enseña");
|
|
2871
|
+
try {
|
|
2872
|
+
const cambios = await opciones.modeloDelCambio(abierto.raiz, sesion, ruta);
|
|
2873
|
+
if (cambios === undefined)
|
|
2874
|
+
return sinNada("no hay con qué comparar");
|
|
2875
|
+
emitir({ clase: "modeloDelCambio", ruta, cambios });
|
|
2876
|
+
}
|
|
2877
|
+
catch (error) {
|
|
2878
|
+
informar(`no se pudo comparar el modelo (${codigoDe(error)})`);
|
|
2879
|
+
sinNada("no se pudo comparar el modelo");
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
/**
|
|
2883
|
+
* Los planes del proyecto abierto. Mismas reglas que el árbol: sin proyecto no se contesta,
|
|
2884
|
+
* sin puerto se contesta con error, y el fallo viaja sin la ruta de la máquina.
|
|
2885
|
+
*/
|
|
2886
|
+
const atenderPlanes = async () => {
|
|
2887
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2888
|
+
if (abierto === undefined)
|
|
2889
|
+
return;
|
|
2890
|
+
if (opciones.planesDelProyecto === undefined) {
|
|
2891
|
+
emitir({ clase: "planes", error: "esta ejecución no puede leer los planes" });
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
try {
|
|
2895
|
+
emitir({ clase: "planes", planes: opciones.planesDelProyecto(abierto.raiz) });
|
|
2896
|
+
}
|
|
2897
|
+
catch (error) {
|
|
2898
|
+
informar(`no se pudieron leer los planes (${codigoDe(error)})`);
|
|
2899
|
+
emitir({ clase: "planes", error: "no se pudieron leer los planes" });
|
|
2900
|
+
}
|
|
2901
|
+
};
|
|
2902
|
+
/**
|
|
2903
|
+
* La foto del modelo XOne del proyecto abierto. Mismas reglas que el árbol: sin proyecto no
|
|
2904
|
+
* se contesta, sin puerto se contesta con error —un «consultando…» eterno es un fallo mudo—,
|
|
2905
|
+
* y el fallo viaja sin la ruta de la máquina.
|
|
2906
|
+
*/
|
|
2907
|
+
const atenderColecciones = async () => {
|
|
2908
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2909
|
+
if (abierto === undefined)
|
|
2910
|
+
return;
|
|
2911
|
+
if (opciones.coleccionesDelProyecto === undefined) {
|
|
2912
|
+
emitir({ clase: "colecciones", error: "esta ejecución no puede leer el modelo del proyecto" });
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
try {
|
|
2916
|
+
emitir({ clase: "colecciones", foto: await opciones.coleccionesDelProyecto(abierto.raiz) });
|
|
2917
|
+
}
|
|
2918
|
+
catch (error) {
|
|
2919
|
+
informar(`no se pudo leer el modelo del proyecto (${codigoDe(error)})`);
|
|
2920
|
+
emitir({ clase: "colecciones", error: "no se pudo leer el modelo del proyecto" });
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2747
2923
|
/**
|
|
2748
2924
|
* El contenido de una ruta del proyecto abierto. El lector decide si se puede enseñar.
|
|
2749
2925
|
*
|
|
@@ -3341,6 +3517,25 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3341
3517
|
return;
|
|
3342
3518
|
}
|
|
3343
3519
|
const descargar = query.get("descargar") !== null;
|
|
3520
|
+
// Un programa de OpenUI se VE dentro de su visor, en un documento autocontenido y sin red
|
|
3521
|
+
// (`visorOpenui.ts`). Descargarlo sigue dando el fichero tal cual.
|
|
3522
|
+
if (!descargar && esArtefactoOpenui(leido.nombre)) {
|
|
3523
|
+
const visor = opciones.visorOpenui?.();
|
|
3524
|
+
if (visor === undefined) {
|
|
3525
|
+
responder(503, "el visor de OpenUI no está construido: ejecuta «npm run build:web»");
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
const documento = Buffer.from(documentoDeOpenui(leido.datos.toString("utf8"), visor, leido.nombre), "utf8");
|
|
3529
|
+
respuesta.writeHead(200, {
|
|
3530
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
3531
|
+
"Content-Length": documento.length,
|
|
3532
|
+
"Content-Security-Policy": CSP_DE_OPENUI,
|
|
3533
|
+
"X-Content-Type-Options": "nosniff",
|
|
3534
|
+
"Cache-Control": "no-store",
|
|
3535
|
+
});
|
|
3536
|
+
respuesta.end(documento);
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3344
3539
|
// Sin mime conocido se descarga en vez de adivinar. El nombre ya pasó la barrera de
|
|
3345
3540
|
// segmento llano, así que entre comillas no puede romper la cabecera.
|
|
3346
3541
|
const inline = !descargar && leido.mime !== undefined;
|
|
@@ -3356,6 +3551,106 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3356
3551
|
});
|
|
3357
3552
|
respuesta.end(leido.datos);
|
|
3358
3553
|
});
|
|
3554
|
+
/**
|
|
3555
|
+
* `GET /imagen-del-proyecto?ruta=<relativa>` — una IMAGEN del proyecto abierto, para que un `.md`
|
|
3556
|
+
* de la pestaña Ficheros enseñe las suyas.
|
|
3557
|
+
*
|
|
3558
|
+
* Existe porque el visor de markdown solo pinta imágenes `http(s)` absolutas: un enlace relativo o
|
|
3559
|
+
* una `data:` se quedaban en su texto alternativo. La vista del documento reescribe cada enlace a
|
|
3560
|
+
* esta ruta (`arbolDeProyecto.ts#vistaDeMarkdown`), y aquí se sirve con la barrera de siempre:
|
|
3561
|
+
* - **Solo imágenes, por su extensión**, antes de leer nada: esto no es un lector de ficheros por
|
|
3562
|
+
* HTTP, y sin esa criba cualquier ruta que se enseñe se podría pedir por aquí.
|
|
3563
|
+
* - **Leída con `leerFichero`** —las dos barreras de la pestaña Ficheros, sobre el texto y sobre
|
|
3564
|
+
* el camino REAL, y su tope—, no con una copia de ellas.
|
|
3565
|
+
* - `Content-Security-Policy: sandbox`, `nosniff` y `no-store`: un `.svg` abierto a pelo en una
|
|
3566
|
+
* pestaña no ejecuta nada, el navegador no adivina el tipo, y una captura reescrita se ve nueva.
|
|
3567
|
+
*/
|
|
3568
|
+
servidor.registrarRuta("GET", RUTA_IMAGEN_DEL_PROYECTO, async (peticion, respuesta) => {
|
|
3569
|
+
const responder = (codigo, texto) => {
|
|
3570
|
+
respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
|
|
3571
|
+
respuesta.end(texto);
|
|
3572
|
+
};
|
|
3573
|
+
const ruta = new URLSearchParams((peticion.url ?? "").split("?")[1] ?? "").get("ruta");
|
|
3574
|
+
if (ruta === null || ruta === "") {
|
|
3575
|
+
responder(400, "falta la ruta");
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3578
|
+
if (mimeDeImagen(ruta) === undefined) {
|
|
3579
|
+
responder(403, "eso no es una imagen");
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3583
|
+
if (abierto === undefined || opciones.leerFichero === undefined) {
|
|
3584
|
+
responder(404, "no hay imagen");
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
let leido;
|
|
3588
|
+
try {
|
|
3589
|
+
leido = await opciones.leerFichero(abierto.raiz, ruta);
|
|
3590
|
+
}
|
|
3591
|
+
catch (error) {
|
|
3592
|
+
// Como en el resto: al cliente y a `informar` solo el código, nunca el mensaje de Node.
|
|
3593
|
+
informar(`no se pudo servir la imagen «${ruta}» (${codigoDe(error)})`);
|
|
3594
|
+
responder(500, "no se pudo leer");
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (leido.error !== undefined) {
|
|
3598
|
+
responder(leido.error === "no existe" ? 404 : 403, leido.error);
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
if (leido.base64 === undefined || leido.mime === undefined) {
|
|
3602
|
+
responder(413, "la imagen es demasiado grande");
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
const datos = Buffer.from(leido.base64, "base64");
|
|
3606
|
+
respuesta.writeHead(200, {
|
|
3607
|
+
"Content-Type": leido.mime,
|
|
3608
|
+
"Content-Length": datos.length,
|
|
3609
|
+
"Content-Security-Policy": "sandbox",
|
|
3610
|
+
"X-Content-Type-Options": "nosniff",
|
|
3611
|
+
"Cache-Control": "no-store",
|
|
3612
|
+
});
|
|
3613
|
+
respuesta.end(datos);
|
|
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
|
+
}
|
|
3359
3654
|
/**
|
|
3360
3655
|
* `POST /adjunto?tarea=<id>&nombre=<fichero>` — los BYTES de un adjunto de tarea.
|
|
3361
3656
|
*
|
|
@@ -3564,6 +3859,31 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3564
3859
|
respuesta.end();
|
|
3565
3860
|
return;
|
|
3566
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
|
+
}
|
|
3567
3887
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "revision") {
|
|
3568
3888
|
void atenderRevision(mensaje.ruta).catch(contar);
|
|
3569
3889
|
respuesta.writeHead(204);
|
|
@@ -3576,6 +3896,27 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3576
3896
|
respuesta.end();
|
|
3577
3897
|
return;
|
|
3578
3898
|
}
|
|
3899
|
+
if (typeof mensaje === "object" &&
|
|
3900
|
+
mensaje !== null &&
|
|
3901
|
+
mensaje.clase === "modeloDelCambio" &&
|
|
3902
|
+
typeof mensaje.ruta === "string") {
|
|
3903
|
+
void atenderModeloDelCambio(mensaje.ruta).catch(contar);
|
|
3904
|
+
respuesta.writeHead(204);
|
|
3905
|
+
respuesta.end();
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "planes") {
|
|
3909
|
+
void atenderPlanes().catch(contar);
|
|
3910
|
+
respuesta.writeHead(204);
|
|
3911
|
+
respuesta.end();
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "colecciones") {
|
|
3915
|
+
void atenderColecciones().catch(contar);
|
|
3916
|
+
respuesta.writeHead(204);
|
|
3917
|
+
respuesta.end();
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3579
3920
|
if (typeof mensaje === "object" &&
|
|
3580
3921
|
mensaje !== null &&
|
|
3581
3922
|
mensaje.clase === "sync" &&
|
|
@@ -3722,6 +4063,84 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3722
4063
|
respuesta.end();
|
|
3723
4064
|
return;
|
|
3724
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
|
+
}
|
|
3725
4144
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "modelosDeMotor" && typeof mensaje.motor === "string") {
|
|
3726
4145
|
void atenderModelosDeMotor(mensaje.motor).catch(contar);
|
|
3727
4146
|
respuesta.writeHead(204);
|
|
@@ -4352,6 +4771,21 @@ export function ajusteDeWorkspaceCableado(opciones) {
|
|
|
4352
4771
|
},
|
|
4353
4772
|
};
|
|
4354
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
|
+
}
|
|
4355
4789
|
export function construirCorredorDeTareasCableado(opciones) {
|
|
4356
4790
|
const disco = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
|
|
4357
4791
|
/**
|
|
@@ -4666,6 +5100,9 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4666
5100
|
// (`ajusteDeWorkspaceCableado`) por el patrón de fallo de siempre — y aquí el escritor
|
|
4667
5101
|
// ya venía con la marca puesta: existía con su test y sin un solo llamador.
|
|
4668
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() }),
|
|
4669
5106
|
// El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
|
|
4670
5107
|
// monta y el botón no llega a existir.
|
|
4671
5108
|
...(haySelectorDeCarpeta()
|
|
@@ -4708,6 +5145,12 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
4708
5145
|
// El proyecto tal como lo ve el agente, para la pestaña Ficheros: mismo filtro, misma
|
|
4709
5146
|
// barrera de rutas (`agent/grafo/arbolDeProyecto.ts`).
|
|
4710
5147
|
arbolDelProyecto: async (raiz) => arbolDeProyecto(raiz),
|
|
5148
|
+
// El modelo XOne con el MISMO cargador y el mismo conjunto de ficheros que la tool, para
|
|
5149
|
+
// que la pestaña y el agente no contesten distinto sobre el mismo proyecto.
|
|
5150
|
+
coleccionesDelProyecto: async (raiz) => fotoDeColecciones(await modeloEnDisco(raiz)(ficherosDelProyecto(raiz))),
|
|
5151
|
+
modeloDelCambio,
|
|
5152
|
+
planesDelProyecto,
|
|
5153
|
+
visorOpenui: () => leerVisorOpenui(raizDelCliente),
|
|
4711
5154
|
leerFichero: leerFicheroDeProyecto,
|
|
4712
5155
|
leerArtefacto: leerArtefactoDeSesion,
|
|
4713
5156
|
leerArtefactoCrudo,
|