xonecode 0.38.15 → 0.38.31
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/{EditorDeFichero-C9BEoDWh.js → EditorDeFichero-CT2vAqMI.js} +1 -1
- package/apps/web/dist/assets/index-DjZ3hYu-.js +506 -0
- package/apps/web/dist/assets/index-oppsOrF4.css +1 -0
- package/apps/web/dist/assets/{php-DIdt3Vgn.js → php-B5s63fYs.js} +1 -1
- package/apps/web/dist/assets/{ruby-DYEvg0Dr.js → ruby-D9QLqlM3.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/conectores/gestorJira.js +118 -1
- package/dist/agent/conectores/gestorJira.js.map +1 -1
- package/dist/agent/dispositivos/decodificarImagen.js +8 -0
- package/dist/agent/dispositivos/decodificarImagen.js.map +1 -1
- package/dist/agent/dispositivos/juezVisual.js +43 -40
- package/dist/agent/dispositivos/juezVisual.js.map +1 -1
- package/dist/agent/grafo/aprendido.js +60 -0
- package/dist/agent/grafo/aprendido.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +17 -5
- package/dist/agent/grafo/criticaVisual.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +2 -0
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/motores/trueforge/capacidades.js +4 -2
- package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
- package/dist/agent/motores/trueforge/convertirAP.js +86 -0
- package/dist/agent/motores/trueforge/convertirAP.js.map +1 -0
- package/dist/agent/motores/trueforge/sesionTrueforge.js +197 -11
- package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
- package/dist/agent/motores/trueforge/traerDeStitch.js +65 -5
- package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +28 -3
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +8 -1
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/pluginDeSkills.js +31 -6
- package/dist/agent/subagentes/pluginDeSkills.js.map +1 -1
- package/dist/agent/subagentes/subagenteExterno.js +28 -9
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/tareas/aumentador.js +13 -2
- package/dist/agent/tareas/aumentador.js.map +1 -1
- package/dist/agent/tareas/juezDeTarea.js +13 -8
- package/dist/agent/tareas/juezDeTarea.js.map +1 -1
- package/dist/agent/tareas/reflejoEnElGestor.js +120 -0
- package/dist/agent/tareas/reflejoEnElGestor.js.map +1 -0
- package/dist/agent/tareas/sondeoDelGestor.js +201 -0
- package/dist/agent/tareas/sondeoDelGestor.js.map +1 -0
- package/dist/agent/turno/resumenDeTool.js +2 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/agent/turno/turnoReal.js +1 -0
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/core/agentes.js +87 -2
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/aprendido.js +68 -0
- package/dist/core/aprendido.js.map +1 -0
- package/dist/core/comandoExterno.js +29 -0
- package/dist/core/comandoExterno.js.map +1 -1
- package/dist/core/compararCapturas.js +62 -4
- package/dist/core/compararCapturas.js.map +1 -1
- package/dist/core/config.js +29 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/convertirAP.js +87 -0
- package/dist/core/convertirAP.js.map +1 -0
- package/dist/core/entrega.js +78 -10
- package/dist/core/entrega.js.map +1 -1
- package/dist/core/geometriaDePantalla.js +107 -5
- package/dist/core/geometriaDePantalla.js.map +1 -1
- package/dist/core/gestorDeTareas.js +2 -0
- package/dist/core/gestorDeTareas.js.map +1 -1
- package/dist/core/idiomaDeLaRespuesta.js +43 -0
- package/dist/core/idiomaDeLaRespuesta.js.map +1 -1
- package/dist/core/metodoDeComparacionVisual.js +71 -0
- package/dist/core/metodoDeComparacionVisual.js.map +1 -0
- package/dist/core/notify.js +4 -0
- package/dist/core/notify.js.map +1 -1
- package/dist/core/nucleoXone.js +42 -0
- package/dist/core/nucleoXone.js.map +1 -1
- package/dist/core/paqueteDeSoporte.js +2 -0
- package/dist/core/paqueteDeSoporte.js.map +1 -1
- package/dist/core/ports.js +36 -2
- package/dist/core/ports.js.map +1 -1
- package/dist/core/stitch.js +43 -0
- package/dist/core/stitch.js.map +1 -1
- package/dist/core/tareas.js.map +1 -1
- package/dist/core/tareasEnElGestor.js +197 -0
- package/dist/core/tareasEnElGestor.js.map +1 -0
- package/dist/core/textos.js +10 -3
- package/dist/core/textos.js.map +1 -1
- package/dist/web/servidor/arranque.js +483 -42
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/consolaDeTarea.js +5 -6
- package/dist/web/servidor/consolaDeTarea.js.map +1 -1
- package/dist/web/servidor/corredorDeTareas.js +33 -4
- package/dist/web/servidor/corredorDeTareas.js.map +1 -1
- package/dist/web/servidor/transporte.js +11 -0
- package/dist/web/servidor/transporte.js.map +1 -1
- package/package.json +2 -2
- package/skills/xone-development/lib/aUtf8.mjs +68 -0
- package/skills/xone-development/references/css/selectores-unidades-colores.md +2 -0
- package/skills/xone-development/scripts/xone-a-utf8 +60 -0
- package/apps/web/dist/assets/index-BpDt_1vN.js +0 -506
- package/apps/web/dist/assets/index-D_Z-V9n1.css +0 -1
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
* en la omisión (Ollama local) con una clave de Anthropic recién escrita al lado.
|
|
29
29
|
*/
|
|
30
30
|
import { hayMemoriaDeHilo, olvidarMemoriaDeHilo } from "../../agent/sesiones/memoriaDeHilo.js";
|
|
31
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
32
32
|
import { lineaDeVersion } from "../../core/version.js";
|
|
33
33
|
import { esperarInterrupcion } from "./interrupcion.js";
|
|
34
34
|
import { basename, dirname, join } from "node:path";
|
|
35
35
|
import { fileURLToPath } from "node:url";
|
|
36
36
|
import { randomUUID } from "node:crypto";
|
|
37
37
|
import { homedir } from "node:os";
|
|
38
|
-
import { escribirAgente, leerAgente, motivoDeNombreInaceptable } from "../../core/agentes.js";
|
|
38
|
+
import { conLoQueLaVentanaNoEdita, escribirAgente, leerAgente, motivoDeNombreInaceptable } from "../../core/agentes.js";
|
|
39
39
|
import { motivoDeNombreDeSkillInaceptable, nombreDeSkillSugerido, } from "../../core/skills.js";
|
|
40
40
|
import { borrarSkill, cargarSkills, esDeSerie as esDeSerieLaSkill, guardarSkill, instalarSkillDesdeZip, } from "../../agent/grafo/skills.js";
|
|
41
41
|
import { TOPE_DEL_ZIP } from "../../core/zipDeSkill.js";
|
|
@@ -125,6 +125,9 @@ import { servicioDeConectoresCableado } from "../../agent/conectores/servicioDeC
|
|
|
125
125
|
import { crearGestorJira } from "../../agent/conectores/gestorJira.js";
|
|
126
126
|
import { crearGestorNotion } from "../../agent/conectores/gestorNotion.js";
|
|
127
127
|
import { admiteMiasDelVinculo, motivoDeProyectoInaceptable, transicionPropuesta, comentarioDeCierre, } from "../../core/gestorDeTareas.js";
|
|
128
|
+
import { cerrarTicket, comentarioDeEncargo, encargoConTicket, esEstado, mapeoPropuesto } from "../../core/tareasEnElGestor.js";
|
|
129
|
+
import { crearSondeoDelGestor } from "../../agent/tareas/sondeoDelGestor.js";
|
|
130
|
+
import { anotarAvisoEnElDisco, conReflejoAlGuardar, crearReflejoEnElGestor } from "../../agent/tareas/reflejoEnElGestor.js";
|
|
128
131
|
import { datosDeCierre } from "./datosDeCierre.js";
|
|
129
132
|
import { ErrorDeDescargaCortada, escribirPaquete, listarSoporte, motivoParaNoExportar } from "./soporte.js";
|
|
130
133
|
import { nombreDelPaquete } from "../../core/paqueteDeSoporte.js";
|
|
@@ -242,6 +245,70 @@ function escaparHtml(texto) {
|
|
|
242
245
|
export function sinCorreos(texto) {
|
|
243
246
|
return texto.replace(/[^\s@<>()"'«»]+@[^\s@<>()"'«»]+\.[^\s@<>()"'«»]+/g, "[correo]");
|
|
244
247
|
}
|
|
248
|
+
/** Un error de un gestor de tareas como texto que puede cruzar el cable y quedarse en una tarea: de Node solo el
|
|
249
|
+
* `code`, y del resto la primera línea con los correos TACHADOS y recortada (la regla de `motivoDelGestor`). */
|
|
250
|
+
export function motivoDeGestorParaElCable(error) {
|
|
251
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
|
|
252
|
+
? error.code
|
|
253
|
+
: sinCorreos((error instanceof Error ? error.message : String(error)).split(/\r?\n/)[0]).slice(0, 200);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Lo que lleva el comentario de cierre de una tarea: lo mismo que el «Cerrar en …» de un chat sobre SU conversación
|
|
257
|
+
* (ficheros y commits de la sesión, el último veredicto del verificador, la última respuesta). Sin conversación, o si
|
|
258
|
+
* no se puede leer, un cierre sin datos —«no corrió», nunca un «verde» inventado—.
|
|
259
|
+
*/
|
|
260
|
+
/**
|
|
261
|
+
* Las copias locales con el DISPARO del gestor encendido (la puerta 2): `<workspace>/<entorno>/<proyecto>` con su
|
|
262
|
+
* `config.json`, de donde salen su identidad (`cloudstudio.proyecto`: el id con el que la pestaña Tareas filtra) y su
|
|
263
|
+
* vínculo. Se relee en cada pasada: el interruptor se cambia con todo en marcha. Lo que no se puede leer se salta.
|
|
264
|
+
*/
|
|
265
|
+
export function copiasConGestor(base) {
|
|
266
|
+
const copias = [];
|
|
267
|
+
const carpetas = (ruta) => {
|
|
268
|
+
try {
|
|
269
|
+
return readdirSync(ruta, { withFileTypes: true })
|
|
270
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
271
|
+
.map((e) => e.name);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
for (const entorno of carpetas(base)) {
|
|
278
|
+
for (const nombre of carpetas(join(base, entorno))) {
|
|
279
|
+
const raiz = join(base, entorno, nombre);
|
|
280
|
+
if (!existsSync(join(raiz, ".xonecode", "config.json")))
|
|
281
|
+
continue;
|
|
282
|
+
try {
|
|
283
|
+
const c = cargar(raiz).config.proyecto;
|
|
284
|
+
const vinculo = c?.gestorDeTareas;
|
|
285
|
+
const identidad = c?.cloudstudio?.proyecto;
|
|
286
|
+
if (vinculo?.automatico !== true || identidad === undefined)
|
|
287
|
+
continue;
|
|
288
|
+
copias.push({ proyecto: { id: identidad.id, raiz, nombre: identidad.nombre }, vinculo });
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// Un config.json roto no para a los demás proyectos.
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return copias;
|
|
296
|
+
}
|
|
297
|
+
export async function datosDeCierreDeTarea(tarea) {
|
|
298
|
+
// Lo crítico y las observaciones con que se cerró (`Tarea.cierre`): van al comentario aunque falle lo demás.
|
|
299
|
+
const delCierre = {
|
|
300
|
+
...((tarea.cierre?.criticos ?? []).length > 0 ? { criticos: tarea.cierre.criticos } : {}),
|
|
301
|
+
...((tarea.cierre?.observaciones ?? []).length > 0 ? { observaciones: tarea.cierre.observaciones } : {}),
|
|
302
|
+
};
|
|
303
|
+
if (tarea.sesion === undefined)
|
|
304
|
+
return { ficheros: [], commits: [], ...delCierre };
|
|
305
|
+
try {
|
|
306
|
+
return { ...(await datosDeCierre(tarea.proyecto.raiz, tarea.sesion)), ...delCierre };
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return { ficheros: [], commits: [], ...delCierre };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
245
312
|
/**
|
|
246
313
|
* El mensaje `gestor` con los campos que su ACCIÓN pide, o `undefined`. Llega del navegador sin
|
|
247
314
|
* tipos: `atenderGestor` confía en esta forma, así que se comprueba aquí y no dentro.
|
|
@@ -962,11 +1029,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
962
1029
|
// mismo fichero que se puede editar a mano, así que el cargador es la única
|
|
963
1030
|
// autoridad sobre si vale. Si no pasara, el agente desaparecería al siguiente
|
|
964
1031
|
// arranque sin que nadie hubiera hecho nada raro.
|
|
965
|
-
|
|
1032
|
+
/**
|
|
1033
|
+
* Los campos que la ventana no edita (`escribeEn`, `llama`, `espera`, `pensamiento`) no viajan por el cable: se
|
|
1034
|
+
* toman del agente que la persona tenía DELANTE —el en vigor con ese nombre, o el de antes en un renombrado—, o
|
|
1035
|
+
* el guardado los borraría (`core/agentes.ts#conLoQueLaVentanaNoEdita`).
|
|
1036
|
+
*/
|
|
1037
|
+
const previo = cargarAgentes(abierto?.raiz).agentes.find((a) => a.nombre === (renombrandoDe ?? mensaje.agente.nombre));
|
|
1038
|
+
const candidato = conLoQueLaVentanaNoEdita(previo, {
|
|
966
1039
|
...mensaje.agente,
|
|
967
1040
|
motor: mensaje.agente.motor,
|
|
968
1041
|
origen: mensaje.ambito,
|
|
969
|
-
};
|
|
1042
|
+
});
|
|
970
1043
|
const comprobado = leerAgente(candidato.nombre, escribirAgente(candidato), mensaje.ambito);
|
|
971
1044
|
if ("error" in comprobado) {
|
|
972
1045
|
informar(`no se guarda «${candidato.nombre}»: ${comprobado.error}`);
|
|
@@ -1640,7 +1713,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1640
1713
|
* que la propia ruta de subida aplica: forma de segmento llano, y que no sea ya una
|
|
1641
1714
|
* tarea.
|
|
1642
1715
|
*/
|
|
1643
|
-
borrador
|
|
1716
|
+
borrador,
|
|
1717
|
+
/** El ticket del gestor YA creado (`atenderCrearTareaEnGestor`): se copia a la tarea antes de guardarla, porque
|
|
1718
|
+
* guardar dispara al corredor y el reflejo de «empezar» tiene que encontrarlo. */
|
|
1719
|
+
ticket) => {
|
|
1644
1720
|
if (opciones.colaDeTareas === undefined)
|
|
1645
1721
|
return undefined;
|
|
1646
1722
|
const proyecto = proyectoParaTarea(proyectoId);
|
|
@@ -1668,10 +1744,88 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1668
1744
|
adjuntos: opciones.colaDeTareas.listarAdjuntos(id),
|
|
1669
1745
|
estado: "nuevo",
|
|
1670
1746
|
creada: new Date().toISOString(),
|
|
1747
|
+
...(ticket === undefined ? {} : { ticket }),
|
|
1671
1748
|
};
|
|
1672
1749
|
opciones.colaDeTareas.guardar([...lista, nueva]);
|
|
1673
1750
|
return { id: nueva.id };
|
|
1674
1751
|
};
|
|
1752
|
+
/**
|
|
1753
|
+
* El gestor en el que vivirá una tarea NUEVA de ese proyecto, si lo tiene: su vínculo (del `config.json` del PROYECTO,
|
|
1754
|
+
* leído aquí y no dicho por el cliente: la ventana se abre también desde el escritorio para otro proyecto) y un
|
|
1755
|
+
* gestor que sabe CREAR. Sin vínculo, o con un gestor sin `crear`, `undefined` y la tarea se encola en local como
|
|
1756
|
+
* siempre (límite declarado: lo dice la ventana).
|
|
1757
|
+
*/
|
|
1758
|
+
const gestorParaTareaNueva = (proyectoId) => {
|
|
1759
|
+
const proyecto = proyectoParaTarea(proyectoId);
|
|
1760
|
+
if (proyecto === undefined)
|
|
1761
|
+
return undefined;
|
|
1762
|
+
let vinculo;
|
|
1763
|
+
try {
|
|
1764
|
+
vinculo = cargar(proyecto.raiz).config.proyecto?.gestorDeTareas;
|
|
1765
|
+
}
|
|
1766
|
+
catch {
|
|
1767
|
+
return undefined;
|
|
1768
|
+
}
|
|
1769
|
+
if (vinculo === undefined)
|
|
1770
|
+
return undefined;
|
|
1771
|
+
const gestor = gestorDe(vinculo.conector);
|
|
1772
|
+
if (gestor?.crear === undefined)
|
|
1773
|
+
return undefined;
|
|
1774
|
+
return { proyecto, vinculo, gestor: gestor };
|
|
1775
|
+
};
|
|
1776
|
+
/**
|
|
1777
|
+
* Crear una tarea EN el gestor (`docs/superpowers/specs/2026-10-15-tareas-desde-el-gestor-design.md`, puerta 1): el
|
|
1778
|
+
* ticket primero, luego el encargo como comentario, y SOLO entonces la tarea local con el ticket puesto. Si el gestor
|
|
1779
|
+
* no deja crear, NO se crea la tarea local —con vínculo, una tarea que no está en el gestor es justo lo que se
|
|
1780
|
+
* pierde— y se contesta 409 con el motivo. Si el ticket se creó pero el comentario no, la tarea se crea igual y se
|
|
1781
|
+
* dice: el ticket ya existe.
|
|
1782
|
+
*/
|
|
1783
|
+
const atenderCrearTareaEnGestor = async (destino, peticion, encargo, borrador) => {
|
|
1784
|
+
if (opciones.colaDeTareas === undefined)
|
|
1785
|
+
return { estado: 409, cuerpo: { motivo: "esta ejecución no tiene cola de tareas" } };
|
|
1786
|
+
// El borrador se comprueba ANTES de tocar el gestor: un ticket creado para una tarea que luego no se guarda quedaría huérfano.
|
|
1787
|
+
if (borrador !== undefined && !esBorradorLibre(borrador, opciones.colaDeTareas.listar())) {
|
|
1788
|
+
return { estado: 409, cuerpo: { motivo: "ese borrador de adjuntos no vale (o ya es una tarea)" } };
|
|
1789
|
+
}
|
|
1790
|
+
const { gestor, vinculo } = destino;
|
|
1791
|
+
// Solo para que el ticket llegue con su enlace: sin la url del sitio se crea igual, sin `url`.
|
|
1792
|
+
await asegurarSitios(gestor).catch(() => { });
|
|
1793
|
+
let creada;
|
|
1794
|
+
try {
|
|
1795
|
+
creada = await gestor.crear(vinculo, { titulo: tituloDeTarea(peticion), descripcion: peticion });
|
|
1796
|
+
}
|
|
1797
|
+
catch (error) {
|
|
1798
|
+
return { estado: 409, cuerpo: { motivo: `no se pudo crear la tarea en el gestor: ${motivoDelGestor(error)}` } };
|
|
1799
|
+
}
|
|
1800
|
+
let aviso;
|
|
1801
|
+
try {
|
|
1802
|
+
await gestor.comentar(vinculo, creada.clave, comentarioDeEncargo(encargo));
|
|
1803
|
+
}
|
|
1804
|
+
catch (error) {
|
|
1805
|
+
aviso = `se creó ${creada.clave}, pero no se pudo subir el encargo: ${motivoDelGestor(error)}`;
|
|
1806
|
+
}
|
|
1807
|
+
const ticket = {
|
|
1808
|
+
conector: vinculo.conector,
|
|
1809
|
+
sitio: vinculo.sitio,
|
|
1810
|
+
proyecto: vinculo.proyecto,
|
|
1811
|
+
clave: creada.clave,
|
|
1812
|
+
...(creada.url === undefined ? {} : { url: creada.url }),
|
|
1813
|
+
...(aviso === undefined ? {} : { aviso }),
|
|
1814
|
+
// El mapeo de estados del vínculo, COPIADO: a qué estado pasa al empezar y al terminar.
|
|
1815
|
+
...(vinculo.estados === undefined ? {} : { estados: vinculo.estados }),
|
|
1816
|
+
};
|
|
1817
|
+
// Con la línea del ticket delante (`encargoConTicket`): en Jira el comentario lleva el encargo SIN ella —ya está en el ticket—.
|
|
1818
|
+
const nueva = atenderCrearTarea(destino.proyecto.id, peticion, encargoConTicket(encargo, ticket), borrador, ticket);
|
|
1819
|
+
if (nueva === undefined) {
|
|
1820
|
+
return { estado: 409, cuerpo: { motivo: `se creó ${creada.clave} en el gestor, pero no la tarea local: créala de nuevo o bórrala allí` } };
|
|
1821
|
+
}
|
|
1822
|
+
opciones.revisarTareas?.();
|
|
1823
|
+
emitirTareas();
|
|
1824
|
+
return {
|
|
1825
|
+
estado: 200,
|
|
1826
|
+
cuerpo: { id: nueva.id, ticket: { clave: creada.clave, ...(creada.url === undefined ? {} : { url: creada.url }) }, ...(aviso === undefined ? {} : { aviso }) },
|
|
1827
|
+
};
|
|
1828
|
+
};
|
|
1675
1829
|
/**
|
|
1676
1830
|
* ¿Se puede adoptar ese id de borrador? Segmento llano Y que no sea ya una tarea.
|
|
1677
1831
|
*
|
|
@@ -3671,6 +3825,9 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3671
3825
|
proyecto: vinculo.proyecto,
|
|
3672
3826
|
...(nombre === undefined ? {} : { nombreDelSitio: nombre }),
|
|
3673
3827
|
...(nombreProyecto === undefined ? {} : { nombreDelProyecto: nombreProyecto }),
|
|
3828
|
+
...(vinculo.estados === undefined ? {} : { estados: vinculo.estados }),
|
|
3829
|
+
...(vinculo.automatico === true ? { automatico: true } : {}),
|
|
3830
|
+
...(vinculo.revisarEncargo === true ? { revisarEncargo: true } : {}),
|
|
3674
3831
|
},
|
|
3675
3832
|
}),
|
|
3676
3833
|
...(admiteMias === undefined ? {} : { admiteMias }),
|
|
@@ -3817,6 +3974,15 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3817
3974
|
proyecto: m.proyecto,
|
|
3818
3975
|
admiteMias,
|
|
3819
3976
|
...(nombreVisto.trim() === "" ? {} : { nombreDelProyecto: nombreVisto }),
|
|
3977
|
+
// Volver a vincular el MISMO proyecto del gestor conserva su mapeo de estados y su disparo; otro proyecto
|
|
3978
|
+
// tiene otro flujo, y empieza sin ellos (apagado).
|
|
3979
|
+
...(anterior !== undefined && anterior.conector === m.conector && anterior.sitio === m.sitio && anterior.proyecto === m.proyecto
|
|
3980
|
+
? {
|
|
3981
|
+
...(anterior.estados === undefined ? {} : { estados: anterior.estados }),
|
|
3982
|
+
...(anterior.automatico === true ? { automatico: true } : {}),
|
|
3983
|
+
...(anterior.revisarEncargo === true ? { revisarEncargo: true } : {}),
|
|
3984
|
+
}
|
|
3985
|
+
: {}),
|
|
3820
3986
|
};
|
|
3821
3987
|
if (!guardar(() => guardarGestorDeProyecto(raiz, vinculo)))
|
|
3822
3988
|
return;
|
|
@@ -3998,20 +4164,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3998
4164
|
// TICKET no hace falta para escribir, así que basta con lo que la sesión guardó al
|
|
3999
4165
|
// abrirse. Se completa con el del proyecto por si acaso; vacío si ya no hay vínculo.
|
|
4000
4166
|
const vinculo = { conector: ticket.conector, sitio: ticket.sitio, proyecto: delProyecto().vinculo?.proyecto ?? "" };
|
|
4001
|
-
//
|
|
4002
|
-
|
|
4003
|
-
//
|
|
4004
|
-
|
|
4005
|
-
// `transicion` y con el motivo en `falloDeTransicion`.
|
|
4006
|
-
let falloDeTransicion;
|
|
4007
|
-
if (m.transicion !== undefined) {
|
|
4008
|
-
try {
|
|
4009
|
-
await g.transicionar(vinculo, ticket.clave, m.transicion);
|
|
4010
|
-
}
|
|
4011
|
-
catch (error) {
|
|
4012
|
-
falloDeTransicion = motivoDelGestor(error);
|
|
4013
|
-
}
|
|
4014
|
-
}
|
|
4167
|
+
// La secuencia de `core/tareasEnElGestor.ts#cerrarTicket`, la MISMA que cierra una tarea en background: si
|
|
4168
|
+
// `comentar` falla, sale al `catch` de fuera y NO se transiciona; si falla la transición, el comentario YA está
|
|
4169
|
+
// y sale `cerrado` sin `transicion` y con el motivo (contestar `error{cerrar}` invitaría a comentar DOS veces).
|
|
4170
|
+
const { falloDeTransicion } = await cerrarTicket(g, vinculo, ticket.clave, m.comentario, m.transicion, motivoDelGestor);
|
|
4015
4171
|
emitir({
|
|
4016
4172
|
clase: "gestor",
|
|
4017
4173
|
cerrado: {
|
|
@@ -5049,14 +5205,34 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5049
5205
|
responder(403, "ese nombre no es de un artefacto");
|
|
5050
5206
|
return;
|
|
5051
5207
|
}
|
|
5052
|
-
|
|
5053
|
-
|
|
5208
|
+
// Sin `p` y `s`, el de la conversación ABIERTA. Con ellos, el de ESA conversación, que se está LEYENDO sin
|
|
5209
|
+
// consola (una tarea finalizada, IXCODE-36): resuelta por el servidor como `leerSesion`, nunca por el texto.
|
|
5210
|
+
let origen;
|
|
5211
|
+
const pedidoProyecto = query.get("p");
|
|
5212
|
+
const pedidaSesion = query.get("s");
|
|
5213
|
+
if (pedidoProyecto !== null || pedidaSesion !== null) {
|
|
5214
|
+
const resuelta = sesionDelDisco(pedidoProyecto, pedidaSesion);
|
|
5215
|
+
if ("motivo" in resuelta) {
|
|
5216
|
+
responder(404, "no hay artefacto");
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
5219
|
+
origen = resuelta;
|
|
5220
|
+
}
|
|
5221
|
+
else {
|
|
5222
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
5223
|
+
if (abierto === undefined) {
|
|
5224
|
+
responder(404, "no hay artefacto");
|
|
5225
|
+
return;
|
|
5226
|
+
}
|
|
5227
|
+
origen = { raiz: abierto.raiz, sesion: abierto.idDeHilo };
|
|
5228
|
+
}
|
|
5229
|
+
if (opciones.leerArtefactoCrudo === undefined) {
|
|
5054
5230
|
responder(404, "no hay artefacto");
|
|
5055
5231
|
return;
|
|
5056
5232
|
}
|
|
5057
5233
|
let leido;
|
|
5058
5234
|
try {
|
|
5059
|
-
leido = await opciones.leerArtefactoCrudo(
|
|
5235
|
+
leido = await opciones.leerArtefactoCrudo(origen.raiz, origen.sesion, nombre);
|
|
5060
5236
|
}
|
|
5061
5237
|
catch (error) {
|
|
5062
5238
|
// El mensaje de Node lleva la ruta absoluta: al cliente solo le llega el código, y a
|
|
@@ -5114,6 +5290,23 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
5114
5290
|
});
|
|
5115
5291
|
respuesta.end(leido.datos);
|
|
5116
5292
|
});
|
|
5293
|
+
/**
|
|
5294
|
+
* Una conversación de un proyecto, resuelta por el SERVIDOR a partir de los IDS que llegan del cable: el nombre
|
|
5295
|
+
* sale del listado del entorno activo, la raíz la compone el vestíbulo y la sesión tiene que estar en el ÍNDICE
|
|
5296
|
+
* del DISCO de ese proyecto. Nunca se lee nada que el cliente nombre. La comparten `leerSesion` y los artefactos
|
|
5297
|
+
* de una conversación que se LEE sin consola (una tarea finalizada): dos copias de esta barrera divergirían.
|
|
5298
|
+
*/
|
|
5299
|
+
const sesionDelDisco = (proyecto, sesion) => {
|
|
5300
|
+
const identidad = proyectos.find((p) => p.id === proyecto);
|
|
5301
|
+
if (entornoElegido === undefined || identidad === undefined)
|
|
5302
|
+
return { motivo: "ese proyecto no está en el listado del entorno activo" };
|
|
5303
|
+
const raiz = vestibulo.raizDeProyecto(entornoElegido, identidad.nombre);
|
|
5304
|
+
if (!esProyectoEnDisco(raiz))
|
|
5305
|
+
return { motivo: "ese proyecto no está descargado en este equipo" };
|
|
5306
|
+
if (typeof sesion !== "string" || !listarSesiones(raiz).some((s) => s.id === sesion))
|
|
5307
|
+
return { motivo: "esa conversación no está en el proyecto" };
|
|
5308
|
+
return { raiz, sesion };
|
|
5309
|
+
};
|
|
5117
5310
|
/**
|
|
5118
5311
|
* Lo que la pestaña Soporte necesita de UN proyecto, compuesto por el SERVIDOR a partir del
|
|
5119
5312
|
* id: la raíz sale del listado del entorno activo (como `resumen` y `borrar`), las tareas se
|
|
@@ -6008,20 +6201,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6008
6201
|
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
6009
6202
|
respuesta.end(JSON.stringify({ motivo }));
|
|
6010
6203
|
};
|
|
6011
|
-
const identidad = proyectos.find((p) => p.id === mensaje.proyecto);
|
|
6012
|
-
const sesion = mensaje.sesion;
|
|
6013
|
-
if (entornoElegido === undefined || identidad === undefined)
|
|
6014
|
-
return negar("ese proyecto no está en el listado del entorno activo");
|
|
6015
|
-
const raiz = vestibulo.raizDeProyecto(entornoElegido, identidad.nombre);
|
|
6016
|
-
if (!esProyectoEnDisco(raiz))
|
|
6017
|
-
return negar("ese proyecto no está descargado en este equipo");
|
|
6018
6204
|
// El índice del DISCO, la misma fuente que lee `reabrirSesion` justo debajo.
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6205
|
+
const resuelta = sesionDelDisco(mensaje.proyecto, mensaje.sesion);
|
|
6206
|
+
if ("motivo" in resuelta)
|
|
6207
|
+
return negar(resuelta.motivo);
|
|
6022
6208
|
let actos;
|
|
6023
6209
|
try {
|
|
6024
|
-
actos = reabrirSesion(raiz, sesion).actos;
|
|
6210
|
+
actos = reabrirSesion(resuelta.raiz, resuelta.sesion).actos;
|
|
6025
6211
|
}
|
|
6026
6212
|
catch (error) {
|
|
6027
6213
|
contar(error);
|
|
@@ -6090,6 +6276,24 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6090
6276
|
contar(error);
|
|
6091
6277
|
resultado = { cerroLaAbierta: false, motivo: "no se pudo borrar la copia local" };
|
|
6092
6278
|
}
|
|
6279
|
+
// **Borrar la copia se lleva también su HISTÓRICO de tareas** (decisión suya): las terminadas de esa raíz,
|
|
6280
|
+
// con su carpeta de adjuntos. Las que no lo están ya han negado el borrado (`motivoParaNoBorrarCopia`), y
|
|
6281
|
+
// su ticket, si lo tenían, se cerró al terminar: aquí no se toca el gestor.
|
|
6282
|
+
if (resultado.borrada === true && opciones.colaDeTareas !== undefined) {
|
|
6283
|
+
const raiz = vestibulo.raizDeProyecto(entornoElegido, identidad.nombre);
|
|
6284
|
+
const suyas = opciones.colaDeTareas
|
|
6285
|
+
.listar()
|
|
6286
|
+
.filter((t) => t.estado === "terminada" && mismaRuta(t.proyecto.raiz, raiz));
|
|
6287
|
+
try {
|
|
6288
|
+
for (const t of suyas)
|
|
6289
|
+
opciones.colaDeTareas.borrarTarea(t.id);
|
|
6290
|
+
}
|
|
6291
|
+
catch (error) {
|
|
6292
|
+
contar(error);
|
|
6293
|
+
}
|
|
6294
|
+
if (suyas.length > 0)
|
|
6295
|
+
emitirTareas();
|
|
6296
|
+
}
|
|
6093
6297
|
// Lo mismo que al borrar la sesión abierta: el cable se quedaría enganchado a una
|
|
6094
6298
|
// consola muerta, así que se muda al vestíbulo —el cliente pinta el escritorio—, y la
|
|
6095
6299
|
// raíz queda LIBRE, así que la cola de tareas se vuelve a mirar.
|
|
@@ -6210,6 +6414,82 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6210
6414
|
respuesta.end();
|
|
6211
6415
|
return;
|
|
6212
6416
|
}
|
|
6417
|
+
if (typeof mensaje === "object" &&
|
|
6418
|
+
mensaje !== null &&
|
|
6419
|
+
mensaje.clase === "gestor" &&
|
|
6420
|
+
(mensaje.accion === "estadosDelFlujo" || mensaje.accion === "ajustarVinculo")) {
|
|
6421
|
+
/**
|
|
6422
|
+
* El MAPEO de estados y el disparo del vínculo del proyecto ABIERTO (la puerta 2), contestados en la propia
|
|
6423
|
+
* respuesta. `estadosDelFlujo` lee del gestor los estados de su flujo y propone el mapeo por categoría;
|
|
6424
|
+
* `ajustarVinculo` lo guarda, comprobando que cada estado elegido EXISTE en ese flujo —un nombre que no está
|
|
6425
|
+
* dejaría el disparo mudo—, y vuelve a emitir el estado del gestor.
|
|
6426
|
+
*/
|
|
6427
|
+
const negar = (motivo) => {
|
|
6428
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
6429
|
+
respuesta.end(JSON.stringify({ motivo }));
|
|
6430
|
+
};
|
|
6431
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
6432
|
+
if (abierto === undefined)
|
|
6433
|
+
return negar("no hay ningún proyecto abierto");
|
|
6434
|
+
let vinculo;
|
|
6435
|
+
try {
|
|
6436
|
+
vinculo = cargar(abierto.raiz).config.proyecto?.gestorDeTareas;
|
|
6437
|
+
}
|
|
6438
|
+
catch {
|
|
6439
|
+
return negar("no se pudo leer la configuración del proyecto");
|
|
6440
|
+
}
|
|
6441
|
+
if (vinculo === undefined)
|
|
6442
|
+
return negar("este proyecto no tiene un gestor de tareas vinculado");
|
|
6443
|
+
const g = gestorDe(vinculo.conector);
|
|
6444
|
+
if (g?.estados === undefined)
|
|
6445
|
+
return negar("este gestor no sabe decir los estados de su flujo");
|
|
6446
|
+
let lista;
|
|
6447
|
+
try {
|
|
6448
|
+
lista = await g.estados(vinculo);
|
|
6449
|
+
}
|
|
6450
|
+
catch (error) {
|
|
6451
|
+
return negar(`no se pudieron leer los estados: ${motivoDelGestor(error)}`);
|
|
6452
|
+
}
|
|
6453
|
+
if (mensaje.accion === "estadosDelFlujo") {
|
|
6454
|
+
respuesta.writeHead(200, { "content-type": "application/json" });
|
|
6455
|
+
respuesta.end(JSON.stringify({ lista, propuesto: mapeoPropuesto(lista), ...(vinculo.estados === undefined ? {} : { actual: vinculo.estados }) }));
|
|
6456
|
+
return;
|
|
6457
|
+
}
|
|
6458
|
+
const pedido = mensaje;
|
|
6459
|
+
if (pedido.revisarEncargo !== undefined && typeof pedido.revisarEncargo !== "boolean")
|
|
6460
|
+
return negar("petición mal formada");
|
|
6461
|
+
if (typeof pedido.automatico !== "boolean" || typeof pedido.estados !== "object" || pedido.estados === null) {
|
|
6462
|
+
return negar("petición mal formada");
|
|
6463
|
+
}
|
|
6464
|
+
const estados = {};
|
|
6465
|
+
for (const papel of ["preparar", "lanzar", "terminar"]) {
|
|
6466
|
+
const e = pedido.estados[papel];
|
|
6467
|
+
if (e === undefined || e === "")
|
|
6468
|
+
continue;
|
|
6469
|
+
if (typeof e !== "string" || !lista.some((x) => esEstado(x.nombre, e)))
|
|
6470
|
+
return negar(`«${String(e)}» no es un estado del flujo de este proyecto`);
|
|
6471
|
+
estados[papel] = lista.find((x) => esEstado(x.nombre, e)).nombre;
|
|
6472
|
+
}
|
|
6473
|
+
// Encender el disparo pide saber cuándo LANZAR: sin ese estado no haría nada y parecería roto.
|
|
6474
|
+
if (pedido.automatico && estados.lanzar === undefined)
|
|
6475
|
+
return negar("para encender el disparo, elige el estado que lanza la tarea");
|
|
6476
|
+
const { estados: _antes, automatico: _a, revisarEncargo: _r, ...resto } = vinculo;
|
|
6477
|
+
try {
|
|
6478
|
+
guardarGestorDeProyecto(abierto.raiz, {
|
|
6479
|
+
...resto,
|
|
6480
|
+
...(Object.keys(estados).length === 0 ? {} : { estados }),
|
|
6481
|
+
...(pedido.automatico ? { automatico: true } : {}),
|
|
6482
|
+
...(pedido.revisarEncargo === true ? { revisarEncargo: true } : {}),
|
|
6483
|
+
});
|
|
6484
|
+
}
|
|
6485
|
+
catch (error) {
|
|
6486
|
+
return negar(`no se pudo guardar en la configuración del proyecto (${codigoDe(error) ?? "su config.json no se puede leer"})`);
|
|
6487
|
+
}
|
|
6488
|
+
void atenderGestor({ clase: "gestor", accion: "estado" }).catch(contar);
|
|
6489
|
+
respuesta.writeHead(204);
|
|
6490
|
+
respuesta.end();
|
|
6491
|
+
return;
|
|
6492
|
+
}
|
|
6213
6493
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "gestor") {
|
|
6214
6494
|
// Los campos se comprueban por ACCIÓN, como `modeloDelCambio`: un mensaje mal formado
|
|
6215
6495
|
// se contesta con su error, no revienta dentro de `atenderGestor`.
|
|
@@ -6272,6 +6552,36 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6272
6552
|
respuesta.end();
|
|
6273
6553
|
return;
|
|
6274
6554
|
}
|
|
6555
|
+
if (typeof mensaje === "object" &&
|
|
6556
|
+
mensaje !== null &&
|
|
6557
|
+
mensaje.clase === "artefacto" &&
|
|
6558
|
+
typeof mensaje.nombre === "string" &&
|
|
6559
|
+
(mensaje.proyecto !== undefined || mensaje.sesion !== undefined)) {
|
|
6560
|
+
// El artefacto de una conversación que se LEE sin consola: se contesta en la PROPIA respuesta y no por el cable,
|
|
6561
|
+
// donde el store lo guardaría por ruta y pisaría el de la conversación abierta con el mismo nombre.
|
|
6562
|
+
const resuelta = sesionDelDisco(mensaje.proyecto, mensaje.sesion);
|
|
6563
|
+
const negar = (motivo) => {
|
|
6564
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
6565
|
+
respuesta.end(JSON.stringify({ motivo }));
|
|
6566
|
+
};
|
|
6567
|
+
if ("motivo" in resuelta)
|
|
6568
|
+
return negar(resuelta.motivo);
|
|
6569
|
+
if (!esRutaDeArtefacto(`${RUTA_ARTEFACTOS}${mensaje.nombre}`))
|
|
6570
|
+
return negar("ese nombre no es de un artefacto");
|
|
6571
|
+
if (opciones.leerArtefacto === undefined)
|
|
6572
|
+
return negar("esta ejecución no puede leer los artefactos");
|
|
6573
|
+
let fichero;
|
|
6574
|
+
try {
|
|
6575
|
+
fichero = await opciones.leerArtefacto(resuelta.raiz, resuelta.sesion, mensaje.nombre);
|
|
6576
|
+
}
|
|
6577
|
+
catch (error) {
|
|
6578
|
+
informar(`no se pudo leer el artefacto «${mensaje.nombre}» (${codigoDe(error)})`);
|
|
6579
|
+
return negar("no se pudo leer el artefacto");
|
|
6580
|
+
}
|
|
6581
|
+
respuesta.writeHead(200, { "content-type": "application/json" });
|
|
6582
|
+
respuesta.end(JSON.stringify(fichero));
|
|
6583
|
+
return;
|
|
6584
|
+
}
|
|
6275
6585
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "artefacto" && typeof mensaje.nombre === "string") {
|
|
6276
6586
|
void atenderArtefacto(mensaje.nombre).catch(contar);
|
|
6277
6587
|
respuesta.writeHead(204);
|
|
@@ -6423,6 +6733,34 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6423
6733
|
respuesta.end();
|
|
6424
6734
|
return;
|
|
6425
6735
|
}
|
|
6736
|
+
if (mensaje.accion === "mirarGestor") {
|
|
6737
|
+
// «Mirar ahora»: una pasada del sondeo de la puerta 2, contestada con lo que hizo. Solo lo hace el proceso que
|
|
6738
|
+
// ejecuta las tareas; en otro, el sondeo no hace nada y lo dice el resultado vacío.
|
|
6739
|
+
if (opciones.mirarGestor === undefined) {
|
|
6740
|
+
respuesta.writeHead(409, { "content-type": "application/json" });
|
|
6741
|
+
respuesta.end(JSON.stringify({ motivo: "esta ejecución no mira el gestor de tareas" }));
|
|
6742
|
+
return;
|
|
6743
|
+
}
|
|
6744
|
+
const r = await opciones.mirarGestor();
|
|
6745
|
+
emitirTareas();
|
|
6746
|
+
respuesta.writeHead(200, { "content-type": "application/json" });
|
|
6747
|
+
respuesta.end(JSON.stringify(r));
|
|
6748
|
+
return;
|
|
6749
|
+
}
|
|
6750
|
+
if (mensaje.accion === "destino") {
|
|
6751
|
+
/**
|
|
6752
|
+
* Dónde vivirá una tarea NUEVA de ese proyecto: con qué gestor, si lo tiene vinculado y sabe crear. Lo pregunta la
|
|
6753
|
+
* ventana «Nueva tarea» al abrirse para decir ANTES del clic qué se va a escribir —crear es la autorización—, y se
|
|
6754
|
+
* decide con la MISMA función que `crear`, así que lo dicho y lo hecho no pueden divergir. Ni red ni gestor: lee el
|
|
6755
|
+
* `config.json` del proyecto. Del vínculo cruza el conector y el nombre del proyecto del gestor, nunca el sitio.
|
|
6756
|
+
*/
|
|
6757
|
+
const destino = gestorParaTareaNueva(mensaje.proyecto);
|
|
6758
|
+
respuesta.writeHead(200, { "content-type": "application/json" });
|
|
6759
|
+
respuesta.end(JSON.stringify(destino === undefined
|
|
6760
|
+
? {}
|
|
6761
|
+
: { gestor: { conector: destino.vinculo.conector, proyecto: destino.vinculo.nombreDelProyecto ?? destino.vinculo.proyecto } }));
|
|
6762
|
+
return;
|
|
6763
|
+
}
|
|
6426
6764
|
if (mensaje.accion === "regenerarEncargo" && opciones.colaDeTareas !== undefined) {
|
|
6427
6765
|
/**
|
|
6428
6766
|
* Volver a PREPARAR el encargo de una tarea que no ha arrancado, desde su petición editada
|
|
@@ -6529,6 +6867,15 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
6529
6867
|
respuesta.end(JSON.stringify({ motivo: "se está cambiando la carpeta de los proyectos: crea la tarea cuando termine" }));
|
|
6530
6868
|
return;
|
|
6531
6869
|
}
|
|
6870
|
+
// Con gestor vinculado, la tarea vive en él: se contesta en la propia respuesta (la ventana no se cierra hasta saber
|
|
6871
|
+
// si el gestor la creó). Sin él, el camino de siempre.
|
|
6872
|
+
const enGestor = mensaje.accion === "crear" ? gestorParaTareaNueva(mensaje.proyecto) : undefined;
|
|
6873
|
+
if (mensaje.accion === "crear" && enGestor !== undefined) {
|
|
6874
|
+
const r = await atenderCrearTareaEnGestor(enGestor, mensaje.peticion, mensaje.encargo, mensaje.borrador);
|
|
6875
|
+
respuesta.writeHead(r.estado, { "content-type": "application/json" });
|
|
6876
|
+
respuesta.end(JSON.stringify(r.cuerpo));
|
|
6877
|
+
return;
|
|
6878
|
+
}
|
|
6532
6879
|
if (mensaje.accion === "crear" && opciones.colaDeTareas !== undefined) {
|
|
6533
6880
|
const creada = atenderCrearTarea(mensaje.proyecto, mensaje.peticion, mensaje.encargo, mensaje.borrador);
|
|
6534
6881
|
if (creada !== undefined) {
|
|
@@ -7098,6 +7445,40 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
7098
7445
|
});
|
|
7099
7446
|
return { emitirTareas, cerrarRemoto: apagarRemoto };
|
|
7100
7447
|
}
|
|
7448
|
+
/**
|
|
7449
|
+
* El sondeo de la puerta 2 CABLEADO: extraído de `arrancarConsolaWeb` para poder probar la composición de verdad (el
|
|
7450
|
+
* patrón de fallo de siempre: una regla de producción en un cierre que todos los tests doblan). Dos cosas que solo se
|
|
7451
|
+
* ven aquí: lo que el sondeo guarda va por la cola ENVUELTA (así el reflejo en el gestor dispara), y tras guardar se
|
|
7452
|
+
* EMITE la cola —el corredor solo emite en sus propias escrituras, y una tarea que no puede arrancar todavía (la
|
|
7453
|
+
* persona tiene la consola de ese proyecto abierta) no salía en la pantalla—. Lo que falla en una pasada, también la
|
|
7454
|
+
* primera, se dice.
|
|
7455
|
+
*/
|
|
7456
|
+
export function construirSondeoCableado(o) {
|
|
7457
|
+
const sondeo = crearSondeoDelGestor({
|
|
7458
|
+
copias: o.copias,
|
|
7459
|
+
gestorDe: o.gestorDe,
|
|
7460
|
+
...(o.augmentar === undefined ? {} : { augmentar: o.augmentar }),
|
|
7461
|
+
cola: o.cola,
|
|
7462
|
+
revisar: () => {
|
|
7463
|
+
o.corredor.revisar();
|
|
7464
|
+
o.emitirTareas();
|
|
7465
|
+
},
|
|
7466
|
+
esDueño: () => o.corredor.corriendoAqui(),
|
|
7467
|
+
motivoDe: motivoDeGestorParaElCable,
|
|
7468
|
+
informar: o.informar,
|
|
7469
|
+
});
|
|
7470
|
+
const decir = (r) => {
|
|
7471
|
+
for (const f of r.fallos)
|
|
7472
|
+
o.informar(`el gestor de ${f.proyecto}${f.clave === undefined ? "" : ` (${f.clave})`}: ${f.motivo}`);
|
|
7473
|
+
if (r.preparadas.length > 0)
|
|
7474
|
+
o.emitirTareas();
|
|
7475
|
+
};
|
|
7476
|
+
return {
|
|
7477
|
+
...sondeo,
|
|
7478
|
+
// «Mirar ahora» y la pasada del arranque dicen sus fallos igual que el reloj.
|
|
7479
|
+
mirar: () => sondeo.mirar().then((r) => (decir(r), r)),
|
|
7480
|
+
};
|
|
7481
|
+
}
|
|
7101
7482
|
/**
|
|
7102
7483
|
* Construye el corredor de tareas y la costura que lo conecta al cable.
|
|
7103
7484
|
*
|
|
@@ -7180,6 +7561,18 @@ export const TOPE_DE_MEMORIA = 4_000;
|
|
|
7180
7561
|
*/
|
|
7181
7562
|
export function contextoDelProyecto(raiz) {
|
|
7182
7563
|
const rama = cloudstudioDelProyecto(raiz)?.rama;
|
|
7564
|
+
// Los conectores marcados para el agente, por su nombre del catálogo; el del gestor de tareas no (en él escribe el harness).
|
|
7565
|
+
let conectores = [];
|
|
7566
|
+
try {
|
|
7567
|
+
const p = cargar(raiz).config.proyecto;
|
|
7568
|
+
const gestor = p?.gestorDeTareas?.conector;
|
|
7569
|
+
conectores = (p?.conectores ?? [])
|
|
7570
|
+
.filter((id) => id !== gestor)
|
|
7571
|
+
.map((id) => CATALOGO_DE_CONECTORES.find((c) => c.id === id)?.nombre ?? id.replace(/^custom:/, ""));
|
|
7572
|
+
}
|
|
7573
|
+
catch {
|
|
7574
|
+
// Un config.json que no se lee: no se afirma ningún conector.
|
|
7575
|
+
}
|
|
7183
7576
|
let memoria;
|
|
7184
7577
|
try {
|
|
7185
7578
|
const leido = readFileSync(rutaMemoriaDeProyecto(raiz), "utf8").trim();
|
|
@@ -7192,6 +7585,7 @@ export function contextoDelProyecto(raiz) {
|
|
|
7192
7585
|
return {
|
|
7193
7586
|
...(rama === undefined || rama === "" ? {} : { rama }),
|
|
7194
7587
|
...(memoria === undefined ? {} : { memoria }),
|
|
7588
|
+
...(conectores.length === 0 ? {} : { conectores }),
|
|
7195
7589
|
};
|
|
7196
7590
|
}
|
|
7197
7591
|
/**
|
|
@@ -7443,6 +7837,7 @@ export function augmentacionCableada(opciones) {
|
|
|
7443
7837
|
proyecto: { nombre: proyecto.nombre, raiz: proyecto.raiz, ...(extra.rama === undefined ? {} : { rama: extra.rama }) },
|
|
7444
7838
|
adjuntos,
|
|
7445
7839
|
...(extra.memoria === undefined ? {} : { memoria: extra.memoria }),
|
|
7840
|
+
...(extra.conectores === undefined ? {} : { conectores: extra.conectores }),
|
|
7446
7841
|
});
|
|
7447
7842
|
};
|
|
7448
7843
|
}
|
|
@@ -7743,7 +8138,27 @@ export function ajusteDeGestorCableado(ajuste) {
|
|
|
7743
8138
|
};
|
|
7744
8139
|
}
|
|
7745
8140
|
export function construirCorredorDeTareasCableado(opciones) {
|
|
7746
|
-
const
|
|
8141
|
+
const crudo = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
|
|
8142
|
+
/**
|
|
8143
|
+
* El almacén con el `guardar` VIGILADO: un único punto por el que pasan todos los cambios de estado (corredor,
|
|
8144
|
+
* reconciliación, rutas, feedback), y de ahí el reflejo en el gestor. Envuelto AQUÍ, donde nace, para que el
|
|
8145
|
+
* corredor y las rutas (`opcionesDeMontaje.colaDeTareas`) compartan el mismo.
|
|
8146
|
+
*/
|
|
8147
|
+
const reflejo = crudo === undefined || opciones.gestorDeTareas === undefined
|
|
8148
|
+
? undefined
|
|
8149
|
+
: crearReflejoEnElGestor({
|
|
8150
|
+
gestorDe: opciones.gestorDeTareas,
|
|
8151
|
+
datosDeCierre: opciones.datosDeCierreDeTarea ?? datosDeCierreDeTarea,
|
|
8152
|
+
// Escribe en disco y AVISA al cable: el corredor solo emite en sus propias escrituras, y sin esto el aviso
|
|
8153
|
+
// («no se pudo pasar KAN-1 a EN CURSO») no salía en la fila hasta que cambiara otra cosa.
|
|
8154
|
+
anotarAviso: (id, aviso) => {
|
|
8155
|
+
anotarAvisoEnElDisco(crudo)(id, aviso);
|
|
8156
|
+
emitirCambioDeTareas?.();
|
|
8157
|
+
},
|
|
8158
|
+
motivoDe: motivoDeGestorParaElCable,
|
|
8159
|
+
informar: opciones.informar,
|
|
8160
|
+
});
|
|
8161
|
+
const disco = crudo === undefined ? undefined : reflejo === undefined ? crudo : conReflejoAlGuardar(crudo, reflejo);
|
|
7747
8162
|
/**
|
|
7748
8163
|
* El tope de concurrencia se LEE de `settings.json` en cada llamada, nunca se cachea —la
|
|
7749
8164
|
* misma disciplina que `ajustesDeDispositivos` (más abajo) y que `sinAprobacion`: la
|
|
@@ -7986,6 +8401,8 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
7986
8401
|
vestibulo: conVestibulo,
|
|
7987
8402
|
tareasFabrica: opciones.tareas,
|
|
7988
8403
|
informar,
|
|
8404
|
+
// El MISMO gestor memoizado que el panel: con él, una tarea con ticket se refleja en su gestor al cambiar de estado.
|
|
8405
|
+
gestorDeTareas: conectoresYGestor.gestorDeTareas,
|
|
7989
8406
|
olvidarHiloDeSesion: olvidarMemoriaDeHilo,
|
|
7990
8407
|
/**
|
|
7991
8408
|
* El juez de QA de las tareas, con el papel `afilado` — el que `core/modelos.ts` le
|
|
@@ -8040,6 +8457,19 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
8040
8457
|
escribir(`${aviso}\n`);
|
|
8041
8458
|
abierto.consola.consola.escribir(`${aviso}\n`);
|
|
8042
8459
|
}
|
|
8460
|
+
/** El aumentador: el de «Nueva tarea» (por las rutas) y el de preparar encargos del sondeo del gestor. Ver su
|
|
8461
|
+
* comentario en las opciones de `montarRutas`. */
|
|
8462
|
+
const augmentar = augmentacionCableada({
|
|
8463
|
+
aumentador: opciones.guion === true
|
|
8464
|
+
? new AumentadorGuionizado()
|
|
8465
|
+
: crearAumentador({
|
|
8466
|
+
invocar: (papel, prompt, raiz) => invocarParaAumentar(new Modelos(fuentesDelJuez(raiz), proveedoresPersonalizados))(papel, prompt, raiz),
|
|
8467
|
+
}),
|
|
8468
|
+
contexto: contextoDelProyecto,
|
|
8469
|
+
});
|
|
8470
|
+
/** El sondeo de la puerta 2: nace con el corredor (más abajo); «Mirar ahora» llega por las rutas, montadas antes. */
|
|
8471
|
+
let sondeo;
|
|
8472
|
+
const espacioDeTrabajo = ajusteDeWorkspaceCableado({ casa: homedir() });
|
|
8043
8473
|
const cable = montarRutas(servidor, vestibulo, {
|
|
8044
8474
|
informar,
|
|
8045
8475
|
// El interruptor se lee de `settings.json` (o `XONECODE_REMOTO=1`) en cada uso. Sin el
|
|
@@ -8239,14 +8669,8 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
8239
8669
|
* - **Con `--guion` se monta el DOBLE**, no el real: esa bandera significa «sin gastar
|
|
8240
8670
|
* ni conectar», y el doble dice en el propio encargo que es de pega (`[DOBLE]`).
|
|
8241
8671
|
*/
|
|
8242
|
-
augmentar
|
|
8243
|
-
|
|
8244
|
-
? new AumentadorGuionizado()
|
|
8245
|
-
: crearAumentador({
|
|
8246
|
-
invocar: (papel, prompt, raiz) => invocarParaAumentar(new Modelos(fuentesDelJuez(raiz), proveedoresPersonalizados))(papel, prompt, raiz),
|
|
8247
|
-
}),
|
|
8248
|
-
contexto: contextoDelProyecto,
|
|
8249
|
-
}),
|
|
8672
|
+
augmentar,
|
|
8673
|
+
mirarGestor: async () => sondeo?.mirar() ?? { preparadas: [], lanzadas: [], fallos: [] },
|
|
8250
8674
|
});
|
|
8251
8675
|
// Con las rutas ya montadas: la reconciliación y el primer despacho cambian la cola, y
|
|
8252
8676
|
// quien conecte después la recibe entera en su ráfaga de bienvenida.
|
|
@@ -8254,6 +8678,23 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
8254
8678
|
// `cable.emitirTareas` y arranca en el orden correcto, así que no hay manera de llamarlo
|
|
8255
8679
|
// mal desde aquí.
|
|
8256
8680
|
await arrancarCorredorConectado(cable);
|
|
8681
|
+
/**
|
|
8682
|
+
* La puerta 2: el gestor de tareas DISPARA tareas (`agent/tareas/sondeoDelGestor.ts`). Solo con cola y corredor, y
|
|
8683
|
+
* solo hace algo en el proceso que EJECUTA las tareas. Una pasada al arrancar recoge lo que se movió con esto parado.
|
|
8684
|
+
*/
|
|
8685
|
+
if (corredor !== undefined && opcionesDeTareas.colaDeTareas !== undefined) {
|
|
8686
|
+
sondeo = construirSondeoCableado({
|
|
8687
|
+
corredor,
|
|
8688
|
+
cola: opcionesDeTareas.colaDeTareas,
|
|
8689
|
+
copias: () => copiasConGestor(espacioDeTrabajo.workspace()),
|
|
8690
|
+
gestorDe: conectoresYGestor.gestorDeTareas,
|
|
8691
|
+
augmentar,
|
|
8692
|
+
emitirTareas: () => cable.emitirTareas(),
|
|
8693
|
+
informar,
|
|
8694
|
+
});
|
|
8695
|
+
sondeo.arrancar();
|
|
8696
|
+
void sondeo.mirar();
|
|
8697
|
+
}
|
|
8257
8698
|
/**
|
|
8258
8699
|
* Con qué código corre ESTE proceso, antes que la URL.
|
|
8259
8700
|
*
|