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.
Files changed (100) hide show
  1. package/apps/web/dist/assets/{EditorDeFichero-C9BEoDWh.js → EditorDeFichero-CT2vAqMI.js} +1 -1
  2. package/apps/web/dist/assets/index-DjZ3hYu-.js +506 -0
  3. package/apps/web/dist/assets/index-oppsOrF4.css +1 -0
  4. package/apps/web/dist/assets/{php-DIdt3Vgn.js → php-B5s63fYs.js} +1 -1
  5. package/apps/web/dist/assets/{ruby-DYEvg0Dr.js → ruby-D9QLqlM3.js} +1 -1
  6. package/apps/web/dist/index.html +2 -2
  7. package/dist/agent/conectores/gestorJira.js +118 -1
  8. package/dist/agent/conectores/gestorJira.js.map +1 -1
  9. package/dist/agent/dispositivos/decodificarImagen.js +8 -0
  10. package/dist/agent/dispositivos/decodificarImagen.js.map +1 -1
  11. package/dist/agent/dispositivos/juezVisual.js +43 -40
  12. package/dist/agent/dispositivos/juezVisual.js.map +1 -1
  13. package/dist/agent/grafo/aprendido.js +60 -0
  14. package/dist/agent/grafo/aprendido.js.map +1 -0
  15. package/dist/agent/grafo/criticaVisual.js +17 -5
  16. package/dist/agent/grafo/criticaVisual.js.map +1 -1
  17. package/dist/agent/grafo/xoneAgent.js +2 -0
  18. package/dist/agent/grafo/xoneAgent.js.map +1 -1
  19. package/dist/agent/motores/trueforge/capacidades.js +4 -2
  20. package/dist/agent/motores/trueforge/capacidades.js.map +1 -1
  21. package/dist/agent/motores/trueforge/convertirAP.js +86 -0
  22. package/dist/agent/motores/trueforge/convertirAP.js.map +1 -0
  23. package/dist/agent/motores/trueforge/sesionTrueforge.js +197 -11
  24. package/dist/agent/motores/trueforge/sesionTrueforge.js.map +1 -1
  25. package/dist/agent/motores/trueforge/traerDeStitch.js +65 -5
  26. package/dist/agent/motores/trueforge/traerDeStitch.js.map +1 -1
  27. package/dist/agent/subagentes/agentesEnDisco.js +28 -3
  28. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  29. package/dist/agent/subagentes/escrituraExterna.js +8 -1
  30. package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
  31. package/dist/agent/subagentes/pluginDeSkills.js +31 -6
  32. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -1
  33. package/dist/agent/subagentes/subagenteExterno.js +28 -9
  34. package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
  35. package/dist/agent/tareas/aumentador.js +13 -2
  36. package/dist/agent/tareas/aumentador.js.map +1 -1
  37. package/dist/agent/tareas/juezDeTarea.js +13 -8
  38. package/dist/agent/tareas/juezDeTarea.js.map +1 -1
  39. package/dist/agent/tareas/reflejoEnElGestor.js +120 -0
  40. package/dist/agent/tareas/reflejoEnElGestor.js.map +1 -0
  41. package/dist/agent/tareas/sondeoDelGestor.js +201 -0
  42. package/dist/agent/tareas/sondeoDelGestor.js.map +1 -0
  43. package/dist/agent/turno/resumenDeTool.js +2 -0
  44. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  45. package/dist/agent/turno/turnoReal.js +1 -0
  46. package/dist/agent/turno/turnoReal.js.map +1 -1
  47. package/dist/cli/consola.js.map +1 -1
  48. package/dist/cli/main.js +5 -0
  49. package/dist/cli/main.js.map +1 -1
  50. package/dist/core/agentes.js +87 -2
  51. package/dist/core/agentes.js.map +1 -1
  52. package/dist/core/aprendido.js +68 -0
  53. package/dist/core/aprendido.js.map +1 -0
  54. package/dist/core/comandoExterno.js +29 -0
  55. package/dist/core/comandoExterno.js.map +1 -1
  56. package/dist/core/compararCapturas.js +62 -4
  57. package/dist/core/compararCapturas.js.map +1 -1
  58. package/dist/core/config.js +29 -0
  59. package/dist/core/config.js.map +1 -1
  60. package/dist/core/convertirAP.js +87 -0
  61. package/dist/core/convertirAP.js.map +1 -0
  62. package/dist/core/entrega.js +78 -10
  63. package/dist/core/entrega.js.map +1 -1
  64. package/dist/core/geometriaDePantalla.js +107 -5
  65. package/dist/core/geometriaDePantalla.js.map +1 -1
  66. package/dist/core/gestorDeTareas.js +2 -0
  67. package/dist/core/gestorDeTareas.js.map +1 -1
  68. package/dist/core/idiomaDeLaRespuesta.js +43 -0
  69. package/dist/core/idiomaDeLaRespuesta.js.map +1 -1
  70. package/dist/core/metodoDeComparacionVisual.js +71 -0
  71. package/dist/core/metodoDeComparacionVisual.js.map +1 -0
  72. package/dist/core/notify.js +4 -0
  73. package/dist/core/notify.js.map +1 -1
  74. package/dist/core/nucleoXone.js +42 -0
  75. package/dist/core/nucleoXone.js.map +1 -1
  76. package/dist/core/paqueteDeSoporte.js +2 -0
  77. package/dist/core/paqueteDeSoporte.js.map +1 -1
  78. package/dist/core/ports.js +36 -2
  79. package/dist/core/ports.js.map +1 -1
  80. package/dist/core/stitch.js +43 -0
  81. package/dist/core/stitch.js.map +1 -1
  82. package/dist/core/tareas.js.map +1 -1
  83. package/dist/core/tareasEnElGestor.js +197 -0
  84. package/dist/core/tareasEnElGestor.js.map +1 -0
  85. package/dist/core/textos.js +10 -3
  86. package/dist/core/textos.js.map +1 -1
  87. package/dist/web/servidor/arranque.js +483 -42
  88. package/dist/web/servidor/arranque.js.map +1 -1
  89. package/dist/web/servidor/consolaDeTarea.js +5 -6
  90. package/dist/web/servidor/consolaDeTarea.js.map +1 -1
  91. package/dist/web/servidor/corredorDeTareas.js +33 -4
  92. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  93. package/dist/web/servidor/transporte.js +11 -0
  94. package/dist/web/servidor/transporte.js.map +1 -1
  95. package/package.json +2 -2
  96. package/skills/xone-development/lib/aUtf8.mjs +68 -0
  97. package/skills/xone-development/references/css/selectores-unidades-colores.md +2 -0
  98. package/skills/xone-development/scripts/xone-a-utf8 +60 -0
  99. package/apps/web/dist/assets/index-BpDt_1vN.js +0 -506
  100. 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
- const candidato = {
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
- // Si `comentar` falla, la excepción sale al `catch` de fuera: NO se transiciona.
4002
- await g.comentar(vinculo, ticket.clave, m.comentario);
4003
- // El comentario YA está en Jira: si la transición falla, contestar `error{cerrar}`
4004
- // invitaría a repetir el cierre entero —y a comentar DOS veces—. Sale `cerrado`, sin
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
- const abierto = vestibulo.proyectoAbierto();
5053
- if (abierto === undefined || opciones.leerArtefactoCrudo === undefined) {
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(abierto.raiz, abierto.idDeHilo, nombre);
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
- if (typeof sesion !== "string" || !listarSesiones(raiz).some((s) => s.id === sesion)) {
6020
- return negar("esa conversación no está en el proyecto");
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 disco = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
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: augmentacionCableada({
8243
- aumentador: opciones.guion === true
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
  *