xonecode 0.5.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/web/dist/assets/{index-BB3SamhR.css → index-Boak51UP.css} +1 -1
- package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
- package/apps/web/dist/assets/{php-D_2--9WK.js → php-Cu6F_QCJ.js} +1 -1
- package/apps/web/dist/assets/{ruby-DSHSDbk1.js → ruby-BiqQPoC6.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/{cloudstudioClient.js → cloudstudio/cloudstudioClient.js} +10 -0
- package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -0
- package/dist/agent/{cloudstudioMcp.js → cloudstudio/cloudstudioMcp.js} +5 -5
- package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -0
- package/dist/agent/{descarga.js → cloudstudio/descarga.js} +17 -7
- package/dist/agent/cloudstudio/descarga.js.map +1 -0
- package/dist/agent/cloudstudio/manifiesto.js.map +1 -0
- package/dist/agent/cloudstudio/ramaActiva.js +11 -0
- package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
- package/dist/agent/{subida.js → cloudstudio/subida.js} +16 -7
- package/dist/agent/cloudstudio/subida.js.map +1 -0
- package/dist/agent/{zip.js → cloudstudio/zip.js} +1 -1
- package/dist/agent/cloudstudio/zip.js.map +1 -0
- package/dist/agent/{authEnDisco.js → config/authEnDisco.js} +3 -3
- package/dist/agent/config/authEnDisco.js.map +1 -0
- package/dist/agent/{catalogoModelos.js → config/catalogoModelos.js} +47 -1
- package/dist/agent/config/catalogoModelos.js.map +1 -0
- package/dist/agent/{configEnDisco.js → config/configEnDisco.js} +6 -6
- package/dist/agent/config/configEnDisco.js.map +1 -0
- package/dist/agent/{crearProyecto.js → config/crearProyecto.js} +2 -2
- package/dist/agent/config/crearProyecto.js.map +1 -0
- package/dist/agent/config/entorno.js.map +1 -0
- package/dist/agent/config/gemini.js.map +1 -0
- package/dist/agent/config/modelos.js +235 -0
- package/dist/agent/config/modelos.js.map +1 -0
- package/dist/agent/{modelosDeMotor.js → config/modelosDeMotor.js} +1 -1
- package/dist/agent/config/modelosDeMotor.js.map +1 -0
- package/dist/agent/config/mudanzaEnDisco.js +121 -0
- package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
- package/dist/agent/{persona.js → config/persona.js} +1 -1
- package/dist/agent/config/persona.js.map +1 -0
- package/dist/agent/config/selectorEnMaquina.js +45 -0
- package/dist/agent/config/selectorEnMaquina.js.map +1 -0
- package/dist/agent/{settingsEnDisco.js → config/settingsEnDisco.js} +37 -2
- package/dist/agent/config/settingsEnDisco.js.map +1 -0
- package/dist/agent/{versionEnDisco.js → config/versionEnDisco.js} +8 -4
- package/dist/agent/config/versionEnDisco.js.map +1 -0
- package/dist/agent/dispositivos/arranqueDeEmulador.js +146 -0
- package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -0
- package/dist/agent/{dispositivosEnMaquina.js → dispositivos/dispositivosEnMaquina.js} +93 -6
- package/dist/agent/dispositivos/dispositivosEnMaquina.js.map +1 -0
- package/dist/agent/dispositivos/hotswap.js.map +1 -0
- package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -0
- package/dist/agent/dispositivos/juezVisual.js +152 -0
- package/dist/agent/dispositivos/juezVisual.js.map +1 -0
- package/dist/agent/{lanzamientoEnMaquina.js → dispositivos/lanzamientoEnMaquina.js} +1 -1
- package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -0
- package/dist/agent/{paqueteDelProyecto.js → dispositivos/paqueteDelProyecto.js} +5 -5
- package/dist/agent/dispositivos/paqueteDelProyecto.js.map +1 -0
- package/dist/agent/dispositivos/procesosEnMaquina.js.map +1 -0
- package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
- package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
- package/dist/agent/exportarPdf.js +140 -0
- package/dist/agent/exportarPdf.js.map +1 -0
- package/dist/agent/{arbolDeProyecto.js → grafo/arbolDeProyecto.js} +2 -2
- package/dist/agent/grafo/arbolDeProyecto.js.map +1 -0
- package/dist/agent/{artefactosEnDisco.js → grafo/artefactosEnDisco.js} +2 -2
- package/dist/agent/grafo/artefactosEnDisco.js.map +1 -0
- package/dist/agent/{busquedaRegex.js → grafo/busquedaRegex.js} +41 -1
- package/dist/agent/grafo/busquedaRegex.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +115 -0
- package/dist/agent/grafo/criticaVisual.js.map +1 -0
- package/dist/agent/grafo/excluirTools.js +82 -0
- package/dist/agent/grafo/excluirTools.js.map +1 -0
- package/dist/agent/{memoriaDeProyecto.js → grafo/memoriaDeProyecto.js} +1 -1
- package/dist/agent/grafo/memoriaDeProyecto.js.map +1 -0
- package/dist/agent/grafo/navegacionXone.js +231 -0
- package/dist/agent/grafo/navegacionXone.js.map +1 -0
- package/dist/agent/{perfiles.js → grafo/perfiles.js} +96 -10
- package/dist/agent/grafo/perfiles.js.map +1 -0
- package/dist/agent/{proyecto.js → grafo/proyecto.js} +274 -24
- package/dist/agent/grafo/proyecto.js.map +1 -0
- package/dist/agent/grafo/skills.js +425 -0
- package/dist/agent/grafo/skills.js.map +1 -0
- package/dist/agent/grafo/xoneAgent.js +566 -0
- package/dist/agent/grafo/xoneAgent.js.map +1 -0
- package/dist/agent/navegacion/estilosEnDisco.js +110 -0
- package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/hechosEnDisco.js +26 -0
- package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/indiceEnDisco.js +36 -0
- package/dist/agent/navegacion/indiceEnDisco.js.map +1 -0
- package/dist/agent/navegacion/modeloDeProyecto.js +228 -0
- package/dist/agent/navegacion/modeloDeProyecto.js.map +1 -0
- package/dist/agent/raizDelPaquete.js +56 -0
- package/dist/agent/raizDelPaquete.js.map +1 -0
- package/dist/agent/{checkpointer.js → sesiones/checkpointer.js} +1 -1
- package/dist/agent/sesiones/checkpointer.js.map +1 -0
- package/dist/agent/sesiones/git.js.map +1 -0
- package/dist/agent/{gitSync.js → sesiones/gitSync.js} +3 -3
- package/dist/agent/sesiones/gitSync.js.map +1 -0
- package/dist/agent/{sesionGit.js → sesiones/sesionGit.js} +2 -2
- package/dist/agent/sesiones/sesionGit.js.map +1 -0
- package/dist/agent/subagentes/agentesEnDisco.js +818 -0
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -0
- package/dist/agent/subagentes/consumoExterno.js.map +1 -0
- package/dist/agent/subagentes/escrituraDeCodex.js.map +1 -0
- package/dist/agent/{escrituraDeOpencode.js → subagentes/escrituraDeOpencode.js} +1 -1
- package/dist/agent/subagentes/escrituraDeOpencode.js.map +1 -0
- package/dist/agent/{escrituraExterna.js → subagentes/escrituraExterna.js} +59 -6
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -0
- package/dist/agent/subagentes/pluginDeSkills.js +99 -0
- package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
- package/dist/agent/subagentes/subagenteCodex.js.map +1 -0
- package/dist/agent/{subagenteExterno.js → subagentes/subagenteExterno.js} +48 -0
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -0
- package/dist/agent/{subagenteOpencode.js → subagentes/subagenteOpencode.js} +53 -1
- package/dist/agent/subagentes/subagenteOpencode.js.map +1 -0
- package/dist/agent/{aumentador.js → tareas/aumentador.js} +2 -2
- package/dist/agent/tareas/aumentador.js.map +1 -0
- package/dist/agent/{juezDeTarea.js → tareas/juezDeTarea.js} +6 -6
- package/dist/agent/tareas/juezDeTarea.js.map +1 -0
- package/dist/agent/{tareasEnDisco.js → tareas/tareasEnDisco.js} +53 -4
- package/dist/agent/tareas/tareasEnDisco.js.map +1 -0
- package/dist/agent/{diagnosticoDeTools.js → turno/diagnosticoDeTools.js} +5 -1
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -0
- package/dist/agent/{guionizado.js → turno/guionizado.js} +1 -1
- package/dist/agent/turno/guionizado.js.map +1 -0
- package/dist/agent/turno/informeDeTraza.js +245 -0
- package/dist/agent/turno/informeDeTraza.js.map +1 -0
- package/dist/agent/turno/inspectorDePrompt.js +205 -0
- package/dist/agent/turno/inspectorDePrompt.js.map +1 -0
- package/dist/agent/{instantanea.js → turno/instantanea.js} +7 -2
- package/dist/agent/turno/instantanea.js.map +1 -0
- package/dist/agent/{interrupts.js → turno/interrupts.js} +2 -2
- package/dist/agent/turno/interrupts.js.map +1 -0
- package/dist/agent/turno/mensajes.js.map +1 -0
- package/dist/agent/turno/normalizar.js.map +1 -0
- package/dist/agent/{puente.js → turno/puente.js} +71 -4
- package/dist/agent/turno/puente.js.map +1 -0
- package/dist/agent/turno/resumenDeContexto.js +260 -0
- package/dist/agent/turno/resumenDeContexto.js.map +1 -0
- package/dist/agent/{resumenDeTool.js → turno/resumenDeTool.js} +33 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -0
- package/dist/agent/turno/rubrica.js +91 -0
- package/dist/agent/turno/rubrica.js.map +1 -0
- package/dist/agent/turno/textoDeTool.js.map +1 -0
- package/dist/agent/{turnoReal.js → turno/turnoReal.js} +210 -25
- package/dist/agent/turno/turnoReal.js.map +1 -0
- package/dist/agent/turno/verificador.js.map +1 -0
- package/dist/cli/config.js +1 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/consola.js +72 -9
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/describe.js +2 -2
- package/dist/cli/describe.js.map +1 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/fuentesDeDisco.js +29 -0
- package/dist/cli/fuentesDeDisco.js.map +1 -0
- package/dist/cli/main.js +87 -31
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.js +40 -10
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/traza.js +48 -0
- package/dist/cli/traza.js.map +1 -0
- package/dist/cli/tui/correrTui.js +4 -4
- package/dist/cli/tui/correrTui.js.map +1 -1
- package/dist/cli/verify.js +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/core/actos.js +1 -1
- package/dist/core/adjuntos.js +2 -2
- package/dist/core/agentes.js +247 -7
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/artefactos.js +4 -4
- package/dist/core/config.js +1 -1
- package/dist/core/contextos.js +12 -1
- package/dist/core/contextos.js.map +1 -1
- package/dist/core/descargas.js +1 -1
- package/dist/core/dispositivos.js +53 -3
- package/dist/core/dispositivos.js.map +1 -1
- package/dist/core/esfuerzo.js +141 -0
- package/dist/core/esfuerzo.js.map +1 -0
- package/dist/core/esqueleto.js +1 -1
- package/dist/core/estilos.js +78 -0
- package/dist/core/estilos.js.map +1 -0
- package/dist/core/exportacion.js +140 -0
- package/dist/core/exportacion.js.map +1 -0
- package/dist/core/hechosDelProyecto.js +87 -0
- package/dist/core/hechosDelProyecto.js.map +1 -0
- package/dist/core/logDeDispositivo.js +143 -0
- package/dist/core/logDeDispositivo.js.map +1 -0
- package/dist/core/modelos.js +48 -3
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/mudanzaDeWorkspace.js +93 -0
- package/dist/core/mudanzaDeWorkspace.js.map +1 -0
- package/dist/core/navegacion.js +134 -0
- package/dist/core/navegacion.js.map +1 -0
- package/dist/core/notify.js +5 -0
- package/dist/core/notify.js.map +1 -1
- package/dist/core/planDeSubida.js +1 -1
- package/dist/core/planes.js +70 -0
- package/dist/core/planes.js.map +1 -0
- package/dist/core/ports.js +4 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/rubrica.js +90 -0
- package/dist/core/rubrica.js.map +1 -0
- package/dist/core/selectorDeCarpeta.js +87 -0
- package/dist/core/selectorDeCarpeta.js.map +1 -0
- package/dist/core/settings.js +56 -7
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +144 -0
- package/dist/core/shellDeAgente.js.map +1 -0
- package/dist/core/skills.js +158 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/topeAgotado.js +69 -0
- package/dist/core/topeAgotado.js.map +1 -0
- package/dist/core/zipDeSkill.js +107 -0
- package/dist/core/zipDeSkill.js.map +1 -0
- package/dist/web/servidor/arranque.js +853 -54
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/corredorDeTareas.js +3 -3
- package/dist/web/servidor/corredorDeTareas.js.map +1 -1
- package/dist/web/servidor/sesiones.js +34 -6
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/transporte.js +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +62 -13
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +7 -4
- package/skills/archify/SKILL.md +25 -2
- package/skills/artifacts-builder/SKILL.md +23 -0
- package/skills/xone-hotswap/SKILL.md +96 -0
- package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
- package/skills/xone-hotswap/scripts/xone-arrancar-ios +85 -0
- package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
- package/skills/xone-hotswap/scripts/xone-desplegar-android +439 -0
- package/skills/xone-hotswap/scripts/xone-hotswap +201 -0
- package/skills/xone-hotswap/scripts/xone-log-android +102 -0
- package/skills/xone-hotswap/scripts/xone-reiniciar-android +132 -0
- package/skills/xone-plan-builder/SKILL.md +22 -4
- package/skills/xone-spec-builder/SKILL.md +37 -7
- package/apps/web/dist/assets/index-CFYAoHdw.js +0 -477
- package/dist/agent/agentesEnDisco.js +0 -539
- package/dist/agent/agentesEnDisco.js.map +0 -1
- package/dist/agent/arbolDeProyecto.js.map +0 -1
- package/dist/agent/artefactosEnDisco.js.map +0 -1
- package/dist/agent/aumentador.js.map +0 -1
- package/dist/agent/authEnDisco.js.map +0 -1
- package/dist/agent/busquedaRegex.js.map +0 -1
- package/dist/agent/catalogoModelos.js.map +0 -1
- package/dist/agent/checkpointer.js.map +0 -1
- package/dist/agent/cloudstudioClient.js.map +0 -1
- package/dist/agent/cloudstudioMcp.js.map +0 -1
- package/dist/agent/configEnDisco.js.map +0 -1
- package/dist/agent/consumoExterno.js.map +0 -1
- package/dist/agent/crearProyecto.js.map +0 -1
- package/dist/agent/descarga.js.map +0 -1
- package/dist/agent/diagnosticoDeTools.js.map +0 -1
- package/dist/agent/dispositivosEnMaquina.js.map +0 -1
- package/dist/agent/entorno.js.map +0 -1
- package/dist/agent/escrituraDeCodex.js.map +0 -1
- package/dist/agent/escrituraDeOpencode.js.map +0 -1
- package/dist/agent/escrituraExterna.js.map +0 -1
- package/dist/agent/gemini.js.map +0 -1
- package/dist/agent/git.js.map +0 -1
- package/dist/agent/gitSync.js.map +0 -1
- package/dist/agent/guionizado.js.map +0 -1
- package/dist/agent/hotswap.js.map +0 -1
- package/dist/agent/instalacionEnMaquina.js.map +0 -1
- package/dist/agent/instantanea.js.map +0 -1
- package/dist/agent/interrupts.js.map +0 -1
- package/dist/agent/juezDeTarea.js.map +0 -1
- package/dist/agent/lanzamientoEnMaquina.js.map +0 -1
- package/dist/agent/manifiesto.js.map +0 -1
- package/dist/agent/memoriaDeProyecto.js.map +0 -1
- package/dist/agent/mensajes.js.map +0 -1
- package/dist/agent/modelos.js +0 -130
- package/dist/agent/modelos.js.map +0 -1
- package/dist/agent/modelosDeMotor.js.map +0 -1
- package/dist/agent/normalizar.js.map +0 -1
- package/dist/agent/paqueteDelProyecto.js.map +0 -1
- package/dist/agent/perfiles.js.map +0 -1
- package/dist/agent/persona.js.map +0 -1
- package/dist/agent/procesosEnMaquina.js.map +0 -1
- package/dist/agent/proyecto.js.map +0 -1
- package/dist/agent/puente.js.map +0 -1
- package/dist/agent/resumenDeContexto.js +0 -30
- package/dist/agent/resumenDeContexto.js.map +0 -1
- package/dist/agent/resumenDeTool.js.map +0 -1
- package/dist/agent/sesionGit.js.map +0 -1
- package/dist/agent/settingsEnDisco.js.map +0 -1
- package/dist/agent/skills.js +0 -65
- package/dist/agent/skills.js.map +0 -1
- package/dist/agent/subagenteCodex.js.map +0 -1
- package/dist/agent/subagenteExterno.js.map +0 -1
- package/dist/agent/subagenteOpencode.js.map +0 -1
- package/dist/agent/subida.js.map +0 -1
- package/dist/agent/tareasEnDisco.js.map +0 -1
- package/dist/agent/textoDeTool.js.map +0 -1
- package/dist/agent/turnoReal.js.map +0 -1
- package/dist/agent/verificador.js.map +0 -1
- package/dist/agent/versionEnDisco.js.map +0 -1
- package/dist/agent/xoneAgent.js +0 -347
- package/dist/agent/xoneAgent.js.map +0 -1
- package/dist/agent/zip.js.map +0 -1
- /package/dist/agent/{manifiesto.js → cloudstudio/manifiesto.js} +0 -0
- /package/dist/agent/{entorno.js → config/entorno.js} +0 -0
- /package/dist/agent/{gemini.js → config/gemini.js} +0 -0
- /package/dist/agent/{hotswap.js → dispositivos/hotswap.js} +0 -0
- /package/dist/agent/{instalacionEnMaquina.js → dispositivos/instalacionEnMaquina.js} +0 -0
- /package/dist/agent/{procesosEnMaquina.js → dispositivos/procesosEnMaquina.js} +0 -0
- /package/dist/agent/{git.js → sesiones/git.js} +0 -0
- /package/dist/agent/{consumoExterno.js → subagentes/consumoExterno.js} +0 -0
- /package/dist/agent/{escrituraDeCodex.js → subagentes/escrituraDeCodex.js} +0 -0
- /package/dist/agent/{subagenteCodex.js → subagentes/subagenteCodex.js} +0 -0
- /package/dist/agent/{mensajes.js → turno/mensajes.js} +0 -0
- /package/dist/agent/{normalizar.js → turno/normalizar.js} +0 -0
- /package/dist/agent/{textoDeTool.js → turno/textoDeTool.js} +0 -0
- /package/dist/agent/{verificador.js → turno/verificador.js} +0 -0
|
@@ -33,9 +33,12 @@ import { basename, dirname, join } from "node:path";
|
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
34
|
import { randomUUID } from "node:crypto";
|
|
35
35
|
import { homedir } from "node:os";
|
|
36
|
-
import { escribirAgente, leerAgente } from "../../core/agentes.js";
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
36
|
+
import { escribirAgente, leerAgente, motivoDeNombreInaceptable } from "../../core/agentes.js";
|
|
37
|
+
import { motivoDeNombreDeSkillInaceptable, nombreDeSkillSugerido, } from "../../core/skills.js";
|
|
38
|
+
import { borrarSkill, cargarSkills, esDeSerie as esDeSerieLaSkill, guardarSkill, instalarSkillDesdeZip, } from "../../agent/grafo/skills.js";
|
|
39
|
+
import { TOPE_DEL_ZIP } from "../../core/zipDeSkill.js";
|
|
40
|
+
import { borrarAgente, cargarAgentes, esDeSerie, guardarAgente, renombrarAgente, restaurarAgente, } from "../../agent/subagentes/agentesEnDisco.js";
|
|
41
|
+
import { detectarDispositivos, frameworkEnDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
|
|
39
42
|
/**
|
|
40
43
|
* El veredicto de «¿se puede lanzar?» y el lanzamiento entero.
|
|
41
44
|
*
|
|
@@ -45,48 +48,53 @@ import { detectarDispositivos, frameworkEnDispositivo } from "../../agent/dispos
|
|
|
45
48
|
* donde puede dejar de estar montado, que es el patrón de fallo de esta casa. Lo único que este
|
|
46
49
|
* lado aporta es `alFase`, que es cómo se cuenta el recorrido.
|
|
47
50
|
*/
|
|
48
|
-
import { lanzarEnDispositivo, } from "../../agent/lanzamientoEnMaquina.js";
|
|
51
|
+
import { lanzarEnDispositivo, } from "../../agent/dispositivos/lanzamientoEnMaquina.js";
|
|
49
52
|
import { motivoDeBloqueo, puedeLanzarse, } from "../../core/puedeLanzarse.js";
|
|
50
53
|
import { PLATAFORMAS_DE_DISPOSITIVO } from "../../core/settings.js";
|
|
51
|
-
import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivosEnMaquina.js";
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
+
import { instalarHerramientaDeDispositivos, verificarDispositivo } from "../../agent/dispositivos/dispositivosEnMaquina.js";
|
|
55
|
+
import { arrancarEmulador } from "../../agent/dispositivos/arranqueDeEmulador.js";
|
|
56
|
+
import { correrPasoDeReceta } from "../../agent/dispositivos/instalacionEnMaquina.js";
|
|
57
|
+
import { modelosDeMotor } from "../../agent/config/modelosDeMotor.js";
|
|
54
58
|
import { parsear, compatibleConOpenAi, esProveedorPersonalizado, idDeProveedorPersonalizado, motivoDeEndpointInaceptable, motivoDeSlugInaceptable, nombreDeProveedor, PROVEEDORES, slugDesdeNombre, resolver, SIN_CREDENCIAL, PAPELES, } from "../../core/modelos.js";
|
|
55
59
|
import { hayCredencial } from "../../cli/consola.js";
|
|
56
60
|
import { motivoDeClaveInaceptable } from "../../core/config.js";
|
|
57
|
-
import { aplicarAuth, aplicarCredencialAlProceso, cargar, guardarModeloGlobal, } from "../../agent/configEnDisco.js";
|
|
58
|
-
import { borrarCredencial, guardarCredencial } from "../../agent/authEnDisco.js";
|
|
59
|
-
import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/configEnDisco.js";
|
|
60
|
-
import { crearCheckpointerDeProyecto, hayCheckpoint, olvidarHilo, } from "../../agent/checkpointer.js";
|
|
61
|
-
import { cargarSettings, guardarConcurrenciaDeTareas, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/settingsEnDisco.js";
|
|
62
|
-
import { dentroDelWorkspace, seAplicaSinAprobacion } from "../../core/settings.js";
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
61
|
+
import { aplicarAuth, aplicarCredencialAlProceso, cargar, guardarModeloGlobal, } from "../../agent/config/configEnDisco.js";
|
|
62
|
+
import { borrarCredencial, guardarCredencial } from "../../agent/config/authEnDisco.js";
|
|
63
|
+
import { borrarProveedorPersonalizado, guardarProveedorPersonalizado, proveedoresPersonalizados, } from "../../agent/config/configEnDisco.js";
|
|
64
|
+
import { crearCheckpointerDeProyecto, hayCheckpoint, olvidarHilo, } from "../../agent/sesiones/checkpointer.js";
|
|
65
|
+
import { cargarSettings, guardarConcurrenciaDeTareas, guardarWorkspace as guardarWorkspaceEnDisco, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/config/settingsEnDisco.js";
|
|
66
|
+
import { dentroDelWorkspace, expandirConCasa, motivoDeWorkspaceInaceptable, seAplicaSinAprobacion, } from "../../core/settings.js";
|
|
67
|
+
import { mudarWorkspaceLegado } from "../../agent/config/mudanzaEnDisco.js";
|
|
68
|
+
import { elegirCarpetaEnMaquina, haySelectorDeCarpeta } from "../../agent/config/selectorEnMaquina.js";
|
|
69
|
+
import { cloudstudioDelProyecto } from "../../agent/config/configEnDisco.js";
|
|
70
|
+
import { abrirEnSistema } from "../../agent/cloudstudio/cloudstudioMcp.js";
|
|
71
|
+
import { nombreDePersona } from "../../agent/config/persona.js";
|
|
72
|
+
import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "../../agent/sesiones/sesionGit.js";
|
|
73
|
+
import { commitDeTurno, cambiosPendientes, trabajoSinCommitear } from "../../agent/sesiones/gitSync.js";
|
|
68
74
|
import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
|
|
69
75
|
import { RUTA_ARTEFACTOS, esRutaDeArtefacto } from "../../core/artefactos.js";
|
|
70
|
-
import { arbolDeProyecto, leerFicheroDeProyecto, motivoDeRutaInaceptable, } from "../../agent/arbolDeProyecto.js";
|
|
71
|
-
import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/artefactosEnDisco.js";
|
|
72
|
-
import { CatalogoModelos } from "../../agent/catalogoModelos.js";
|
|
73
|
-
import { Modelos } from "../../agent/modelos.js";
|
|
74
|
-
import { crearJuezDeTarea, invocarConModelos } from "../../agent/juezDeTarea.js";
|
|
76
|
+
import { arbolDeProyecto, leerFicheroDeProyecto, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
|
|
77
|
+
import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/grafo/artefactosEnDisco.js";
|
|
78
|
+
import { CatalogoModelos, baseUrlDeOllama, baseUrlDeOllamaCloud, capacidadesDeOllama, } from "../../agent/config/catalogoModelos.js";
|
|
79
|
+
import { Modelos } from "../../agent/config/modelos.js";
|
|
80
|
+
import { crearJuezDeTarea, invocarConModelos } from "../../agent/tareas/juezDeTarea.js";
|
|
81
|
+
import { SIN_CONSUMO } from "../../agent/subagentes/consumoExterno.js";
|
|
75
82
|
import { AumentadorGuionizado } from "../../core/ports.js";
|
|
76
83
|
import { arrancarServidor } from "./servidor.js";
|
|
77
84
|
import { consolaParaTarea, crearCorredorDeTareas, revisionConGit, } from "./corredorDeTareas.js";
|
|
78
85
|
import { CONCURRENCIA_POR_OMISION, conEstado, darPorBuenaAMano, tituloDeTarea, } from "../../core/tareas.js";
|
|
79
|
-
import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareasEnDisco.js";
|
|
86
|
+
import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareas/tareasEnDisco.js";
|
|
80
87
|
import { nombreDeAdjuntoAceptable } from "../../core/adjuntos.js";
|
|
81
|
-
import { rutaMemoriaDeProyecto } from "../../agent/memoriaDeProyecto.js";
|
|
82
|
-
import { crearAumentador, invocarParaAumentar } from "../../agent/aumentador.js";
|
|
83
|
-
import { baseDeWorkspacePorOmision, conexionDeVestibulo, crearVestibulo, escribirProyectoEnDisco, esProyectoEnDisco, } from "./vestibulo.js";
|
|
88
|
+
import { rutaMemoriaDeProyecto } from "../../agent/grafo/memoriaDeProyecto.js";
|
|
89
|
+
import { crearAumentador, invocarParaAumentar } from "../../agent/tareas/aumentador.js";
|
|
90
|
+
import { baseDeWorkspaceLegada, baseDeWorkspacePorOmision, conexionDeVestibulo, crearVestibulo, escribirProyectoEnDisco, esProyectoEnDisco, } from "./vestibulo.js";
|
|
84
91
|
// Valor y no tipo: la traducción de una `Tarea` a lo que viaja vive JUNTO al tipo que
|
|
85
92
|
// produce y no aquí. Estuvo en este cierre, y ahí se cayó `veredicto` sin que nada se
|
|
86
93
|
// pusiera rojo (F1 de la revisión final) — el patrón de fallo que este repo ya tiene
|
|
87
94
|
// documentado cinco veces: una composición de producción dentro de algo que los tests
|
|
88
95
|
// doblan.
|
|
89
96
|
import { filaDeTarea } from "./transporte.js";
|
|
97
|
+
import { esEsfuerzo, nivelesDeEsfuerzo } from "../../core/esfuerzo.js";
|
|
90
98
|
/** Las dos rutas del cable. El cliente las tiene escritas en `apps/web/src/conexion.ts`. */
|
|
91
99
|
/**
|
|
92
100
|
* Cuánto se espera al aviso de «lo que ya había sin commitear» antes de anunciar sin él.
|
|
@@ -97,6 +105,24 @@ import { filaDeTarea } from "./transporte.js";
|
|
|
97
105
|
* definición de que el aviso frena, que es justo lo que su propia regla prohíbe.
|
|
98
106
|
*/
|
|
99
107
|
export const MS_DE_TRABAJO_AL_ABRIR = 2000;
|
|
108
|
+
/**
|
|
109
|
+
* Cuánto se espera al arranque antes de entrar igual.
|
|
110
|
+
*
|
|
111
|
+
* Lo que se prepara es la sesión MCP y la lista de proyectos del primer entorno registrado,
|
|
112
|
+
* y el usuario pidió no entrar al Escritorio hasta tenerlo: entrar antes significa un
|
|
113
|
+
* Escritorio vacío que se rellena delante. Pero esperar SIN plazo es lo que convierte una
|
|
114
|
+
* espera en un cuelgue — medido con un MCP que descarta paquetes, el lienzo se quedaba para
|
|
115
|
+
* siempre, y eso es peor que lo que veníamos a arreglar.
|
|
116
|
+
*
|
|
117
|
+
* Generoso a propósito: una sesión MCP fría lleva descubrimiento OAuth y handshake, y
|
|
118
|
+
* cortarla pronto haría entrar en vacío justo a quien tiene la red lenta, que es a quien más
|
|
119
|
+
* le duele. Y como el lienzo DICE lo que está haciendo (`preparando`), la espera se lee como
|
|
120
|
+
* trabajo y no como una pantalla muerta.
|
|
121
|
+
*
|
|
122
|
+
* Igual que `MS_DE_TRABAJO_AL_ABRIR`, lo que se acota es la ESPERA y no el trabajo: la
|
|
123
|
+
* promesa sigue viva y lo que llegue tarde sale en el siguiente anuncio.
|
|
124
|
+
*/
|
|
125
|
+
export const MS_DE_PREPARACION = 10_000;
|
|
100
126
|
export const RUTA_EVENTOS = "/eventos";
|
|
101
127
|
export const RUTA_ACCION = "/accion";
|
|
102
128
|
/**
|
|
@@ -109,6 +135,13 @@ export const RUTA_ACCION = "/accion";
|
|
|
109
135
|
* el contrato de las skills que los escriben es «autocontenido».
|
|
110
136
|
*/
|
|
111
137
|
export const RUTA_ARTEFACTO = "/artefacto";
|
|
138
|
+
/**
|
|
139
|
+
* `POST /skill?nombre=<fichero.zip>&ambito=<global|proyecto>` — los BYTES de un `.zip`.
|
|
140
|
+
*
|
|
141
|
+
* Por HTTP y no por el cable, que lleva JSON: la misma razón y el mismo molde que
|
|
142
|
+
* `POST /adjunto`. Una ruta EXACTA, porque el servidor casa por ruta exacta.
|
|
143
|
+
*/
|
|
144
|
+
export const RUTA_SKILL = "/skill";
|
|
112
145
|
/**
|
|
113
146
|
* `POST /adjunto?tarea=<id>&nombre=<fichero>` — los BYTES de un adjunto.
|
|
114
147
|
*
|
|
@@ -245,6 +278,14 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
245
278
|
* en la Trayectoria —la otra pestaña—, y el wizard repintaba el mismo paso sin decir nada.
|
|
246
279
|
*/
|
|
247
280
|
let aviso;
|
|
281
|
+
/**
|
|
282
|
+
* Qué está preparando el arranque, mientras lo prepara. `undefined` = listo.
|
|
283
|
+
*
|
|
284
|
+
* Vive aquí y no en el anuncio porque el `alta` se emite también en los dos flancos de
|
|
285
|
+
* cada turno, y esos anuncios no están preparando nada: quien lo pone y lo quita es el
|
|
286
|
+
* arranque de la conexión, y los demás leen lo que haya.
|
|
287
|
+
*/
|
|
288
|
+
let preparando;
|
|
248
289
|
const destinoActual = () => vestibulo.proyectoAbierto() ?? vestibulo.consola;
|
|
249
290
|
const emitir = (mensaje) => {
|
|
250
291
|
for (const cliente of clientes)
|
|
@@ -436,6 +477,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
436
477
|
: {}),
|
|
437
478
|
...(vestibulo.nombre === undefined ? {} : { nombre: vestibulo.nombre }),
|
|
438
479
|
...(aviso === undefined ? {} : { aviso }),
|
|
480
|
+
// Ausente = listo, y solo entonces el cliente sale del lienzo. Se lee de la variable
|
|
481
|
+
// en cada anuncio en vez de pasarse por parámetro: los anuncios de los flancos del
|
|
482
|
+
// turno no preparan nada y tienen que decir lo que hay, no lo que había.
|
|
483
|
+
...(preparando === undefined ? {} : { preparando }),
|
|
439
484
|
});
|
|
440
485
|
};
|
|
441
486
|
/**
|
|
@@ -444,6 +489,18 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
444
489
|
* recuerda —como fallo— hasta que alguien lo vuelva a pedir a propósito.
|
|
445
490
|
*/
|
|
446
491
|
const catalogos = new Map();
|
|
492
|
+
/**
|
|
493
|
+
* Lo que el servidor de Ollama ha dicho de cada modelo, por `proveedor/modelo`.
|
|
494
|
+
*
|
|
495
|
+
* Es una memoria y no una caché con plazo: se llena la primera vez que ese modelo está en
|
|
496
|
+
* vigor y se vuelve a emitir el estado cuando llega. Ollama es el único que necesita esto
|
|
497
|
+
* —los demás salen de la tabla, que es síncrona—, y lo necesita porque sus modelos los
|
|
498
|
+
* elige el usuario: no hay prefijo que diga si `ministral-3:3b` piensa, pero `/api/show`
|
|
499
|
+
* sí lo contesta.
|
|
500
|
+
*/
|
|
501
|
+
const capacidadesVivas = new Map();
|
|
502
|
+
/** Las que ya se están preguntando, para no lanzar una consulta por cliente conectado. */
|
|
503
|
+
const capacidadesEnVuelo = new Set();
|
|
447
504
|
/**
|
|
448
505
|
* El estado de modelos: qué está en vigor y qué se puede elegir.
|
|
449
506
|
*
|
|
@@ -451,7 +508,13 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
451
508
|
* proyecto abierto no hay sesión y por tanto no hay modelo que afirmar: el campo se va
|
|
452
509
|
* y el cliente pinta «Elige modelo» en vez de una fila muerta.
|
|
453
510
|
*/
|
|
454
|
-
const emitirModelos = () =>
|
|
511
|
+
const emitirModelos = () => {
|
|
512
|
+
emitir(mensajeDeModelos());
|
|
513
|
+
// Después de emitir, no antes: lo que ya se sabe se pinta ya, y lo que falta llega en
|
|
514
|
+
// una segunda emisión. Al revés, cada cambio de modelo dejaría la pastilla esperando
|
|
515
|
+
// una consulta de red para pintar lo que no depende de ella.
|
|
516
|
+
asegurarCapacidades();
|
|
517
|
+
};
|
|
455
518
|
/**
|
|
456
519
|
* Los subagentes en vigor, compuestos pero sin mandar.
|
|
457
520
|
*
|
|
@@ -471,9 +534,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
471
534
|
motor: a.motor,
|
|
472
535
|
...(a.modelo === undefined ? {} : { modelo: a.modelo }),
|
|
473
536
|
soloLectura: a.soloLectura,
|
|
537
|
+
// Ausente = no la tiene. No se emite `false` para que el cliente no tenga que
|
|
538
|
+
// distinguir entre «no» y «no consta»: aquí son lo mismo y la ausencia lo dice.
|
|
539
|
+
...(a.ejecucion === true ? { ejecucion: true } : {}),
|
|
540
|
+
// Igual que arriba: ausente es «no hay ninguno fijado», que es un dato y no un hueco.
|
|
541
|
+
...(a.esfuerzo === undefined ? {} : { esfuerzo: a.esfuerzo }),
|
|
474
542
|
skills: a.skills,
|
|
475
543
|
instrucciones: a.instrucciones,
|
|
476
544
|
origen: a.origen,
|
|
545
|
+
// Ausente cuando el `.md` es del usuario, y eso es el dato: es lo que le pone la
|
|
546
|
+
// papelera en vez de un «Restaurar el de serie».
|
|
547
|
+
...(a.semilla === undefined ? {} : { semilla: a.semilla }),
|
|
477
548
|
})),
|
|
478
549
|
problemas,
|
|
479
550
|
};
|
|
@@ -489,6 +560,52 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
489
560
|
*/
|
|
490
561
|
const atenderAgente = (mensaje) => {
|
|
491
562
|
const abierto = vestibulo.proyectoAbierto();
|
|
563
|
+
/**
|
|
564
|
+
* `restaurar` va ANTES de la guarda del ámbito porque no tiene ámbito que elegir: la
|
|
565
|
+
* siembra solo escribe en el global, así que la versión de serie solo puede volver ahí.
|
|
566
|
+
* Pasarlo por el `ambito` del mensaje sería ofrecer una decisión que no existe, y con un
|
|
567
|
+
* `ambito: "proyecto"` daría un «no hay ningún proyecto abierto» que no viene a cuento.
|
|
568
|
+
*/
|
|
569
|
+
if (mensaje.accion === "restaurar") {
|
|
570
|
+
informar(restaurarAgente(homedir(), mensaje.agente.nombre)
|
|
571
|
+
? `subagente «${mensaje.agente.nombre}» restaurado al de serie`
|
|
572
|
+
: `«${mensaje.agente.nombre}» no es uno de los de serie: no hay ninguna versión nuestra que poner`);
|
|
573
|
+
emitirAgentes();
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Y un de serie NO se borra. La negativa vive AQUÍ y no solo en el cliente, que le
|
|
578
|
+
* esconde el icono: esconderlo es presentación, y este mensaje lo puede mandar cualquier
|
|
579
|
+
* cosa que hable por el cable. Borrarlo no devolvía el de serie —`sembrarAgentes` no
|
|
580
|
+
* resiembra lo que consta entregado— así que era perderlo para siempre; lo que sí hace
|
|
581
|
+
* lo que aquello prometía es `restaurar`, y se dice en el mismo sitio donde se rechaza.
|
|
582
|
+
*/
|
|
583
|
+
if (mensaje.accion === "borrar" && mensaje.ambito === "global" && esDeSerie(mensaje.agente.nombre)) {
|
|
584
|
+
informar(`«${mensaje.agente.nombre}» lo trae xonecode y no se borra: edítalo, o restaura el de serie si quieres el original`);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Un renombrado de verdad: `renombrandoDe` puesto y DISTINTO del nombre nuevo. Con el
|
|
589
|
+
* mismo nombre es un guardado normal, y tratarlo de renombrado lo haría fallar por
|
|
590
|
+
* «destino ocupado» contra sí mismo.
|
|
591
|
+
*/
|
|
592
|
+
const renombrandoDe = mensaje.accion === "guardar" &&
|
|
593
|
+
mensaje.renombrandoDe !== undefined &&
|
|
594
|
+
mensaje.renombrandoDe !== mensaje.agente.nombre
|
|
595
|
+
? mensaje.renombrandoDe
|
|
596
|
+
: undefined;
|
|
597
|
+
/**
|
|
598
|
+
* Y un de serie tampoco se RENOMBRA, por lo mismo que no se borra: su nombre es lo que lo
|
|
599
|
+
* ata a la marca de la siembra (`.semilla.json` guarda el hash POR NOMBRE), así que
|
|
600
|
+
* moverlo lo convierte en un subagente del usuario y hace que el de serie vuelva a
|
|
601
|
+
* sembrarse al arrancar — dos especialistas donde había uno. La regla vive aquí y no en
|
|
602
|
+
* `renombrarAgente`, que mueve un fichero: dos sitios donde decidir lo mismo es cómo uno
|
|
603
|
+
* de los dos se queda sin la regla.
|
|
604
|
+
*/
|
|
605
|
+
if (renombrandoDe !== undefined && mensaje.ambito === "global" && esDeSerie(renombrandoDe)) {
|
|
606
|
+
informar(`«${renombrandoDe}» lo trae xonecode y su nombre no se cambia: es lo que lo ata a la versión de serie`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
492
609
|
if (mensaje.ambito === "proyecto" && abierto === undefined) {
|
|
493
610
|
informar("no hay ningún proyecto abierto: ese subagente solo se puede guardar como global");
|
|
494
611
|
return;
|
|
@@ -515,8 +632,35 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
515
632
|
informar(`no se guarda «${candidato.nombre}»: ${comprobado.error}`);
|
|
516
633
|
return;
|
|
517
634
|
}
|
|
518
|
-
|
|
519
|
-
|
|
635
|
+
/**
|
|
636
|
+
* Y el NOMBRE tiene que ser un slug, aquí y no en el cargador: cargando solo se DICE
|
|
637
|
+
* (`leerCarpetaDeAgentes`), porque rechazar al cargar haría desaparecer un subagente
|
|
638
|
+
* que funciona. Guardar es el único momento con alguien delante para arreglarlo.
|
|
639
|
+
*
|
|
640
|
+
* No lo cubre `leerAgente`: el nombre no sale del `.md`, sale del FICHERO, así que
|
|
641
|
+
* escribir y volver a leer no lo mira. Y tampoco `segmentoSeguro`, que impide salir de
|
|
642
|
+
* la carpeta y nada más — `Documentador` pasa esa y no es un slug.
|
|
643
|
+
*/
|
|
644
|
+
const malNombre = motivoDeNombreInaceptable(candidato.nombre);
|
|
645
|
+
if (malNombre !== undefined) {
|
|
646
|
+
informar(`no se guarda «${candidato.nombre}»: el nombre ${malNombre}`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (renombrandoDe === undefined) {
|
|
650
|
+
guardarAgente(base, candidato);
|
|
651
|
+
informar(`subagente «${candidato.nombre}» guardado en ${mensaje.ambito}`);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
// Los tres desenlaces se dicen distintos: el destino ocupado y el origen que ya no
|
|
655
|
+
// está son dos situaciones que el usuario arregla de formas distintas, y un «no se
|
|
656
|
+
// pudo» para las dos le haría probar la equivocada.
|
|
657
|
+
const como = renombrarAgente(base, renombrandoDe, candidato);
|
|
658
|
+
informar(como === "hecho"
|
|
659
|
+
? `subagente «${renombrandoDe}» renombrado a «${candidato.nombre}»`
|
|
660
|
+
: como === "sin-origen"
|
|
661
|
+
? `«${renombrandoDe}» ya no existía en ${mensaje.ambito}: no se ha renombrado`
|
|
662
|
+
: `ya hay un subagente «${candidato.nombre}» en ${mensaje.ambito}: elige otro nombre, o bórralo primero`);
|
|
663
|
+
}
|
|
520
664
|
}
|
|
521
665
|
}
|
|
522
666
|
catch (error) {
|
|
@@ -524,6 +668,132 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
524
668
|
}
|
|
525
669
|
emitirAgentes();
|
|
526
670
|
};
|
|
671
|
+
/**
|
|
672
|
+
* Las skills en vigor, compuestas pero sin mandar.
|
|
673
|
+
*
|
|
674
|
+
* `cargarSkills` cada vez y no una lista cacheada, por lo mismo que `mensajeDeAgentes`:
|
|
675
|
+
* las carpetas se pueden tocar a mano con la consola abierta, y una lista congelada al
|
|
676
|
+
* arrancar haría creer que el cambio no se aplicó.
|
|
677
|
+
*
|
|
678
|
+
* **El `cuerpo` viaja solo en las del USUARIO.** Las de serie son nueve ficheros que pasan
|
|
679
|
+
* de veinte mil caracteres cada uno; mandarlos en la ráfaga de bienvenida es pagar cientos
|
|
680
|
+
* de kilobytes por rellenar un formulario que nadie puede guardar. El suyo se pide a mano
|
|
681
|
+
* (`cuerpoDeSkill`), que es cuando alguien la está mirando de verdad.
|
|
682
|
+
*/
|
|
683
|
+
const mensajeDeSkills = () => {
|
|
684
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
685
|
+
const { skills, problemas } = cargarSkills(abierto?.raiz);
|
|
686
|
+
return {
|
|
687
|
+
clase: "skills",
|
|
688
|
+
skills: skills.map((s) => ({
|
|
689
|
+
nombre: s.nombre,
|
|
690
|
+
descripcion: s.descripcion,
|
|
691
|
+
origen: s.origen,
|
|
692
|
+
tokens: s.tokens,
|
|
693
|
+
ficheros: s.ficheros,
|
|
694
|
+
// El frontmatter va SIEMPRE: son cuatro líneas, y es lo que contesta qué declara el
|
|
695
|
+
// fichero. El CUERPO no, en las de serie: eso sí son decenas de miles de caracteres.
|
|
696
|
+
...(s.frontmatter === undefined ? {} : { frontmatter: s.frontmatter }),
|
|
697
|
+
...(s.origen === "serie" ? {} : { cuerpo: s.cuerpo }),
|
|
698
|
+
})),
|
|
699
|
+
problemas,
|
|
700
|
+
};
|
|
701
|
+
};
|
|
702
|
+
const emitirSkills = () => emitir(mensajeDeSkills());
|
|
703
|
+
/**
|
|
704
|
+
* El cuerpo de UNA skill, bajo demanda: al abrir la ficha de una de serie o al copiarla.
|
|
705
|
+
*
|
|
706
|
+
* Se contesta SOLO a quien lo pidió no se puede —el transporte habla con todos—, así que
|
|
707
|
+
* va a todos y el cliente se queda con la que tiene abierta. Es un fichero de texto que ya
|
|
708
|
+
* está en la lista, no un secreto.
|
|
709
|
+
*
|
|
710
|
+
* Lo que no se pudo leer se DICE con un `error`: una ficha que se abre en blanco se lee
|
|
711
|
+
* como que la skill está vacía, y entonces copiarla produciría una copia vacía.
|
|
712
|
+
*/
|
|
713
|
+
const atenderCuerpoDeSkill = (mensaje) => {
|
|
714
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
715
|
+
const skill = cargarSkills(abierto?.raiz).skills.find((s) => s.nombre === mensaje.nombre);
|
|
716
|
+
emitir(skill === undefined
|
|
717
|
+
? { clase: "cuerpoDeSkill", nombre: mensaje.nombre, error: "ya no está en el catálogo" }
|
|
718
|
+
: { clase: "cuerpoDeSkill", nombre: mensaje.nombre, cuerpo: skill.cuerpo });
|
|
719
|
+
};
|
|
720
|
+
/**
|
|
721
|
+
* Alta, cambio y borrado de una skill del usuario. El mismo molde que `atenderAgente`.
|
|
722
|
+
*
|
|
723
|
+
* **Las de serie no se tocan, y la barrera está AQUÍ.** Viven dentro del paquete
|
|
724
|
+
* instalado: una edición se la llevaría el siguiente `npm install` sin decir nada, y un
|
|
725
|
+
* borrado no se puede reponer porque nada nuestro se copia nunca a la casa del usuario
|
|
726
|
+
* —no hay marca de siembra que consultar, a diferencia de los subagentes—. Lo que la
|
|
727
|
+
* consola ofrece sobre una de serie es COPIARLA, que es un `guardar` con otro nombre.
|
|
728
|
+
*
|
|
729
|
+
* `guardarSkill` DEVUELVE el motivo en vez de lanzar, así que los tres desenlaces —el
|
|
730
|
+
* nombre ocupado, el nombre de una de serie y el fallo de escritura— se dicen distintos:
|
|
731
|
+
* son tres cosas que el usuario arregla de tres formas.
|
|
732
|
+
*/
|
|
733
|
+
const atenderSkill = (mensaje) => {
|
|
734
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
735
|
+
const nombre = mensaje.skill.nombre;
|
|
736
|
+
if (mensaje.ambito === "proyecto" && abierto === undefined) {
|
|
737
|
+
informar("no hay ningún proyecto abierto: esa skill solo se puede guardar como global");
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const base = mensaje.ambito === "proyecto" ? abierto.raiz : homedir();
|
|
741
|
+
/**
|
|
742
|
+
* Borrar se INTENTA y el motivo sale de lo que pasó, no de una guarda previa por NOMBRE.
|
|
743
|
+
*
|
|
744
|
+
* La guarda por nombre era demasiado ancha: una skill del usuario que se llame como una
|
|
745
|
+
* de serie —tapándola, que es la forma de afinarla sin editar donde el `npm install` la
|
|
746
|
+
* pisaría— no se podía borrar nunca, porque `esDeSerie` mira el catálogo del paquete y no
|
|
747
|
+
* la carpeta del usuario. Lo que hay en la carpeta del usuario es del usuario y se borra;
|
|
748
|
+
* lo que no está ahí y ADEMÁS lo traemos nosotros es lo que se niega, con el camino al
|
|
749
|
+
* lado. Un solo sitio decide, que es `borrarSkill` contestando si había algo.
|
|
750
|
+
*/
|
|
751
|
+
if (mensaje.accion === "borrar") {
|
|
752
|
+
informar(borrarSkill(base, nombre)
|
|
753
|
+
? `skill «${nombre}» borrada`
|
|
754
|
+
: esDeSerieLaSkill(nombre)
|
|
755
|
+
? `la skill «${nombre}» la trae xonecode y no se borra: cópiala si quieres partir de ella`
|
|
756
|
+
: `«${nombre}» no existía en ${mensaje.ambito}`);
|
|
757
|
+
emitirSkills();
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* El nombre tiene que ser un slug, y se comprueba al GUARDAR y no al cargar: rechazar al
|
|
762
|
+
* cargar haría desaparecer una skill que funciona, y guardar es el único momento con
|
|
763
|
+
* alguien delante para arreglarlo. `segmentoSeguro` —que `guardarSkill` sí aplica— solo
|
|
764
|
+
* impide salir de la carpeta: `Mi Skill` la pasa y no es un slug, y el nombre de una
|
|
765
|
+
* skill viaja además dentro del `.md` de un subagente.
|
|
766
|
+
*/
|
|
767
|
+
const malNombre = motivoDeNombreDeSkillInaceptable(nombre);
|
|
768
|
+
if (malNombre !== undefined) {
|
|
769
|
+
const sugerido = nombreDeSkillSugerido(nombre);
|
|
770
|
+
informar(`no se guarda «${nombre}»: el nombre ${malNombre}` +
|
|
771
|
+
(sugerido === undefined ? "" : ` (prueba «${sugerido}»)`));
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (mensaje.skill.descripcion.trim() === "") {
|
|
775
|
+
// Es lo que el modelo lee para decidir si le sirve: sin ella la skill está en el
|
|
776
|
+
// catálogo y no la usa nadie nunca, que es peor que no estar.
|
|
777
|
+
informar(`no se guarda «${nombre}»: sin descripción, el orquestador no sabría cuándo usarla`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const renombrandoDe = mensaje.renombrandoDe !== undefined && mensaje.renombrandoDe !== nombre
|
|
781
|
+
? mensaje.renombrandoDe
|
|
782
|
+
: undefined;
|
|
783
|
+
const escrita = guardarSkill(base, {
|
|
784
|
+
nombre,
|
|
785
|
+
descripcion: mensaje.skill.descripcion,
|
|
786
|
+
cuerpo: mensaje.skill.cuerpo ?? "",
|
|
787
|
+
// El frontmatter de ANTES, para conservar lo que no editamos. Ausente en un alta.
|
|
788
|
+
...(mensaje.skill.frontmatter === undefined ? {} : { frontmatter: mensaje.skill.frontmatter }),
|
|
789
|
+
}, mensaje.renombrandoDe);
|
|
790
|
+
informar("error" in escrita
|
|
791
|
+
? `no se guarda «${nombre}»: ${escrita.error}`
|
|
792
|
+
: renombrandoDe === undefined
|
|
793
|
+
? `skill «${nombre}» guardada en ${mensaje.ambito}`
|
|
794
|
+
: `skill «${renombrandoDe}» renombrada a «${nombre}»`);
|
|
795
|
+
emitirSkills();
|
|
796
|
+
};
|
|
527
797
|
/** El mensaje de modelos, compuesto pero sin mandar: `adjuntar` se lo da SOLO al cliente
|
|
528
798
|
* que acaba de llegar, y el resto de sitios lo emite a todos. */
|
|
529
799
|
const mensajeDeModelos = () => {
|
|
@@ -534,10 +804,43 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
534
804
|
// pueda leer el config global. Sin el segundo, Ajustes enseñaría «sin elegir» sobre una
|
|
535
805
|
// máquina con un defecto escrito, justo en la pantalla donde se configura.
|
|
536
806
|
const porDefecto = opciones.modeloPorDefecto?.();
|
|
807
|
+
/**
|
|
808
|
+
* El esfuerzo del modelo EN VIGOR, que es lo único de lo que se puede afirmar algo.
|
|
809
|
+
*
|
|
810
|
+
* Sin sesión no hay modelo en vigor y no viaja: la pastilla no se pinta, igual que
|
|
811
|
+
* `actual` no se inventa. Los niveles salen de la tabla pura; para Ollama hacen falta
|
|
812
|
+
* las capacidades vivas, y mientras no hayan llegado tampoco se afirma nada — se
|
|
813
|
+
* piden aparte y el estado se vuelve a emitir cuando contesten.
|
|
814
|
+
*/
|
|
815
|
+
const niveles = trabajo === undefined
|
|
816
|
+
? undefined
|
|
817
|
+
: nivelesDeEsfuerzo(trabajo.proveedor, trabajo.modelo, capacidadesVivas.get(`${trabajo.proveedor}/${trabajo.modelo}`));
|
|
818
|
+
const elegido = abierto?.estadoDeSesion.esfuerzo;
|
|
537
819
|
return {
|
|
538
820
|
clase: "modelos",
|
|
539
821
|
...(trabajo === undefined ? {} : { actual: `${trabajo.proveedor}/${trabajo.modelo}` }),
|
|
540
822
|
...(porDefecto === undefined ? {} : { porDefecto }),
|
|
823
|
+
...(niveles === undefined
|
|
824
|
+
? {}
|
|
825
|
+
: {
|
|
826
|
+
esfuerzo: {
|
|
827
|
+
niveles: [...niveles],
|
|
828
|
+
// El nivel guardado solo viaja si ESTE modelo lo admite. Si no, la pastilla
|
|
829
|
+
// diría «xhigh» sobre un modelo que va a ignorarlo — el valor sigue en la
|
|
830
|
+
// sesión y vuelve solo al cambiar a un modelo que lo acepte.
|
|
831
|
+
...(elegido !== undefined && niveles.includes(elegido) ? { actual: elegido } : {}),
|
|
832
|
+
// La nota de Ollama, MEDIDA: su servidor valida los cuatro niveles, pero el
|
|
833
|
+
// efecto lo pone el template de cada modelo y no es monótono —en
|
|
834
|
+
// `glm-5.3-flash:cloud`, `high` mide menos razonamiento que `medium`—.
|
|
835
|
+
// Callarlo haría que la pastilla afirmara una escala que ahí no existe.
|
|
836
|
+
...(trabajo?.proveedor === "ollama" || trabajo?.proveedor === "ollama-cloud"
|
|
837
|
+
? {
|
|
838
|
+
nota: "En Ollama el efecto de cada nivel lo decide el modelo, no el servidor:"
|
|
839
|
+
+ " medido, en algunos «high» razona MENOS que «medium».",
|
|
840
|
+
}
|
|
841
|
+
: {}),
|
|
842
|
+
},
|
|
843
|
+
}),
|
|
541
844
|
proveedores: [...PROVEEDORES, ...idsPersonalizados()].map((p) => ({
|
|
542
845
|
id: p,
|
|
543
846
|
nombre: nombreDeProveedor(p, personalizados()),
|
|
@@ -598,13 +901,34 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
598
901
|
for (const cliente of destinatarios)
|
|
599
902
|
actos = destino.conectar(cliente);
|
|
600
903
|
const modelos = mensajeDeModelos();
|
|
904
|
+
/**
|
|
905
|
+
* Y se pregunta por lo que falte, que en esta rama hay que pedir A MANO.
|
|
906
|
+
*
|
|
907
|
+
* La ráfaga COMPONE el mensaje en vez de emitirlo —va solo al recién llegado—, así que
|
|
908
|
+
* no pasa por `emitirModelos` y sin esta línea el camino más normal de todos —abrir el
|
|
909
|
+
* navegador sobre un proyecto ya abierto— no preguntaba nunca: la pastilla de esfuerzo
|
|
910
|
+
* no aparecía con Ollama hasta que algo más provocara una emisión. Lo encontró el test
|
|
911
|
+
* del cable, que es exactamente el patrón de fallo de esta arquitectura.
|
|
912
|
+
*
|
|
913
|
+
* No se mete DENTRO de `mensajeDeModelos` porque eso es un compositor: un efecto de red
|
|
914
|
+
* escondido en la función que arma un mensaje es la clase de cosa que luego dispara
|
|
915
|
+
* desde sitios que nadie esperaba.
|
|
916
|
+
*/
|
|
917
|
+
asegurarCapacidades();
|
|
601
918
|
// Los subagentes van en la ráfaga por lo mismo que los modelos: la ventana de ajustes
|
|
602
919
|
// se puede abrir en cuanto conecta, y sin esto enseñaría una lista vacía hasta que algo
|
|
603
920
|
// los cambiara — que es indistinguible de «no tienes ninguno».
|
|
604
921
|
const agentes = mensajeDeAgentes();
|
|
922
|
+
// Y las skills, por lo mismo que los subagentes y con una razón más: el editor de un
|
|
923
|
+
// subagente pinta sus skills como una lista de casillas, así que sin esto ese formulario
|
|
924
|
+
// se abriría sin ninguna casilla que marcar — que es indistinguible de «no hay ninguna».
|
|
925
|
+
const skills = mensajeDeSkills();
|
|
605
926
|
// La cola de tareas, si esta ejecución las tiene: la misma regla que `agentes`, sin
|
|
606
927
|
// esto la pestaña de tareas se quedaría vacía hasta el primer cambio de la cola.
|
|
607
928
|
const tareas = mensajeDeTareas();
|
|
929
|
+
// El workspace, por lo mismo que los subagentes y las skills: Ajustes se puede abrir en
|
|
930
|
+
// cuanto conecta, y un campo en blanco se lee como «no hay ninguno puesto».
|
|
931
|
+
const workspace = mensajeDeWorkspace();
|
|
608
932
|
/**
|
|
609
933
|
* Lo consumido por la sesión que se va a pintar. Va en la ráfaga por lo mismo que los
|
|
610
934
|
* modelos: una pestaña que conecta a mitad de sesión no vio los cambios anteriores, y
|
|
@@ -619,8 +943,11 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
619
943
|
cliente({ clase: "reemision", actos: [...actos] });
|
|
620
944
|
cliente(modelos);
|
|
621
945
|
cliente(agentes);
|
|
946
|
+
cliente(skills);
|
|
622
947
|
if (tareas !== undefined)
|
|
623
948
|
cliente(tareas);
|
|
949
|
+
if (workspace !== undefined)
|
|
950
|
+
cliente(workspace);
|
|
624
951
|
if (consumoDeLaSesion !== undefined) {
|
|
625
952
|
cliente({
|
|
626
953
|
clase: "consumo",
|
|
@@ -708,20 +1035,33 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
708
1035
|
* heredar un «ya se intentó» que se quedó en `proyectos: []` para siempre. Esto puede
|
|
709
1036
|
* abrir el navegador de verdad si el token necesita reautenticar —`conectarCloudStudio`
|
|
710
1037
|
* ya lo hace así—, y es lo correcto: un token vivo no toca el puerto de callback en
|
|
711
|
-
* absoluto (arreglado en `agent/cloudstudioMcp.ts#abrirCliente`), así que dos conexiones
|
|
1038
|
+
* absoluto (arreglado en `agent/cloudstudio/cloudstudioMcp.ts#abrirCliente`), así que dos conexiones
|
|
712
1039
|
* seguidas no chocan por intentarlo cada una.
|
|
713
1040
|
*/
|
|
714
|
-
|
|
1041
|
+
/**
|
|
1042
|
+
* De qué entorno se poblarían los proyectos al arrancar, o nada si no hay nada que
|
|
1043
|
+
* preparar.
|
|
1044
|
+
*
|
|
1045
|
+
* Es la MISMA decisión que toma `poblarProyectosSiProcede`, extraída para que el arranque
|
|
1046
|
+
* pueda anunciar la fase ANTES de empezarla. En dos sitios divergirían, y el precio sería
|
|
1047
|
+
* de los peores: el lienzo esperando por un trabajo que nadie va a pedir (`preparando`
|
|
1048
|
+
* puesto y nada que lo quite), o entrar en vacío justo cuando sí había algo que traer.
|
|
1049
|
+
*/
|
|
1050
|
+
const entornoAPoblar = () => {
|
|
715
1051
|
if (vestibulo.proyectoAbierto() !== undefined)
|
|
716
|
-
return;
|
|
1052
|
+
return undefined;
|
|
717
1053
|
if (entornoElegido !== undefined)
|
|
718
|
-
return;
|
|
1054
|
+
return undefined;
|
|
719
1055
|
const [primero] = vestibulo.entornosRegistrados();
|
|
720
|
-
|
|
1056
|
+
return primero === undefined ? undefined : { id: primero.id, nombre: primero.nombre };
|
|
1057
|
+
};
|
|
1058
|
+
const poblarProyectosSiProcede = async () => {
|
|
1059
|
+
const destino = entornoAPoblar();
|
|
1060
|
+
if (destino === undefined)
|
|
721
1061
|
return;
|
|
722
1062
|
try {
|
|
723
|
-
proyectos = await vestibulo.proyectosDe(
|
|
724
|
-
entornoElegido =
|
|
1063
|
+
proyectos = await vestibulo.proyectosDe(destino.id);
|
|
1064
|
+
entornoElegido = destino.id;
|
|
725
1065
|
}
|
|
726
1066
|
catch (error) {
|
|
727
1067
|
aviso = error instanceof Error ? error.message : String(error);
|
|
@@ -746,6 +1086,21 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
746
1086
|
});
|
|
747
1087
|
/** Los cuatro nombres conocidos y nada más: lo que llega por el cable no elige binario. */
|
|
748
1088
|
const esNombreDeHerramienta = (v) => v === "adb" || v === "emulator" || v === "xcrun" || v === "devicectl";
|
|
1089
|
+
const mensajeDeWorkspace = () => {
|
|
1090
|
+
const ruta = opciones.workspace?.();
|
|
1091
|
+
if (ruta === undefined)
|
|
1092
|
+
return undefined;
|
|
1093
|
+
// `puedeElegir` va solo cuando es cierto: ausente ya significa que no, y un `false`
|
|
1094
|
+
// escrito solo daría dos formas de decir lo mismo.
|
|
1095
|
+
return opciones.elegirCarpeta === undefined
|
|
1096
|
+
? { clase: "workspace", ruta }
|
|
1097
|
+
: { clase: "workspace", ruta, puedeElegir: true };
|
|
1098
|
+
};
|
|
1099
|
+
const emitirWorkspace = () => {
|
|
1100
|
+
const m = mensajeDeWorkspace();
|
|
1101
|
+
if (m !== undefined)
|
|
1102
|
+
emitir(m);
|
|
1103
|
+
};
|
|
749
1104
|
const mensajeDeTareas = () => {
|
|
750
1105
|
if (opciones.colaDeTareas === undefined)
|
|
751
1106
|
return undefined;
|
|
@@ -1033,7 +1388,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1033
1388
|
/**
|
|
1034
1389
|
* «Se edita la tarea y se agrega el feedback del usuario» (§0 del diseño, textual):
|
|
1035
1390
|
* añadir un feedback a una tarea «esperando feedback» la devuelve al lazo. Vive detrás de
|
|
1036
|
-
* `aplicarFeedback` (`agent/tareasEnDisco.ts`) y no repite su lógica aquí, por el mismo
|
|
1391
|
+
* `aplicarFeedback` (`agent/tareas/tareasEnDisco.ts`) y no repite su lógica aquí, por el mismo
|
|
1037
1392
|
* motivo que `atenderCrearTarea`/`atenderAccionDeTarea` no reimplementan `conEstado`: la
|
|
1038
1393
|
* regla de qué feedback vale y qué transición es legal está en una sola función, probada
|
|
1039
1394
|
* sola y sin necesitar un servidor de mentira alrededor.
|
|
@@ -1051,6 +1406,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1051
1406
|
/** Los ajustes de destino, o `{}` —que es «se miran todos»— si esta ejecución no los lee. */
|
|
1052
1407
|
const ajustesDeDispositivos = () => opciones.ajustesDeDispositivos?.() ?? {};
|
|
1053
1408
|
let deteccionEnVuelo;
|
|
1409
|
+
/**
|
|
1410
|
+
* Cómo acabó el último «Arrancar». Viaja con la foto porque el resultado de arrancar ES la
|
|
1411
|
+
* foto: `emulator` devuelve el control enseguida y lo que importa es si el aparato apareció.
|
|
1412
|
+
* Se limpia al pedir una medida sin arranque de por medio, para no repetir un acuse viejo.
|
|
1413
|
+
*/
|
|
1414
|
+
let ultimoArranque;
|
|
1054
1415
|
const atenderDispositivos = () => {
|
|
1055
1416
|
if (opciones.detectarDispositivos === undefined)
|
|
1056
1417
|
return Promise.resolve();
|
|
@@ -1060,7 +1421,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1060
1421
|
deteccionEnVuelo = (async () => {
|
|
1061
1422
|
try {
|
|
1062
1423
|
informeDeDispositivos = sinRutas(await detectar());
|
|
1063
|
-
emitir({
|
|
1424
|
+
emitir({
|
|
1425
|
+
clase: "dispositivos",
|
|
1426
|
+
informe: informeDeDispositivos,
|
|
1427
|
+
ajustes: ajustesDeDispositivos(),
|
|
1428
|
+
...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
|
|
1429
|
+
});
|
|
1064
1430
|
}
|
|
1065
1431
|
catch (error) {
|
|
1066
1432
|
// El detector reparte los fallos por herramienta y no lanza por ninguno; que
|
|
@@ -1075,6 +1441,55 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1075
1441
|
})();
|
|
1076
1442
|
return deteccionEnVuelo;
|
|
1077
1443
|
};
|
|
1444
|
+
/**
|
|
1445
|
+
* **Arrancar un AVD, y contarlo con la foto de después.**
|
|
1446
|
+
*
|
|
1447
|
+
* Tres guardas, en este orden:
|
|
1448
|
+
*
|
|
1449
|
+
* 1. **Que esta ejecución pueda.** Sin el puerto no hay arranque y se DICE: un botón que
|
|
1450
|
+
* llega a un servidor que no sabe arrancar nada es el control muerto de siempre.
|
|
1451
|
+
* 2. **Que ese AVD esté en NUESTRA última medida.** El nombre llega por el cable y acaba
|
|
1452
|
+
* siendo un argumento de proceso; la lista de AVDs es lo único que lo autoriza. Sin
|
|
1453
|
+
* medida todavía no se arranca nada — no hay con qué comprobarlo.
|
|
1454
|
+
* 3. **Uno a la vez.** Dos arranques del mismo AVD a la vez son dos emuladores peleándose
|
|
1455
|
+
* por el mismo puerto, y de otro AVD sería trabajo pesado en paralelo en la máquina de
|
|
1456
|
+
* quien mira. Se reenvía el estado, que es lo que hace la receta.
|
|
1457
|
+
*
|
|
1458
|
+
* Al acabar se vuelve a medir SIEMPRE, salga bien o mal: la foto es la que dice si arrancó.
|
|
1459
|
+
*/
|
|
1460
|
+
let arranqueEnVuelo;
|
|
1461
|
+
const atenderArranqueDeEmulador = async (avd) => {
|
|
1462
|
+
const arrancar = opciones.arrancarEmulador;
|
|
1463
|
+
if (arrancar === undefined) {
|
|
1464
|
+
informar("esta ejecución no puede arrancar emuladores");
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
if (arranqueEnVuelo !== undefined) {
|
|
1468
|
+
informar(`ya estoy arrancando ${arranqueEnVuelo}`);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
if (informeDeDispositivos === undefined || !informeDeDispositivos.avds.includes(avd)) {
|
|
1472
|
+
ultimoArranque = { avd, ok: false, detalle: `no consta ningún AVD llamado «${avd}» en la última medida` };
|
|
1473
|
+
await atenderDispositivos().catch(contar);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
arranqueEnVuelo = avd;
|
|
1477
|
+
try {
|
|
1478
|
+
const r = await arrancar(avd);
|
|
1479
|
+
ultimoArranque = { avd, ...r };
|
|
1480
|
+
}
|
|
1481
|
+
catch (error) {
|
|
1482
|
+
// El puerto promete no lanzar; si lanzara, el botón no puede quedarse mudo.
|
|
1483
|
+
ultimoArranque = { avd, ok: false, detalle: error instanceof Error ? error.message : String(error) };
|
|
1484
|
+
contar(error);
|
|
1485
|
+
}
|
|
1486
|
+
finally {
|
|
1487
|
+
arranqueEnVuelo = undefined;
|
|
1488
|
+
// La medida nueva, pase lo que pase: es la que dice la verdad.
|
|
1489
|
+
informeDeDispositivos = undefined;
|
|
1490
|
+
await atenderDispositivos().catch(contar);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1078
1493
|
/**
|
|
1079
1494
|
* Verificar la conexión con UN dispositivo, y volver a emitir la foto con lo que contestó.
|
|
1080
1495
|
*
|
|
@@ -1125,11 +1540,32 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1125
1540
|
}
|
|
1126
1541
|
try {
|
|
1127
1542
|
const modelos = await opciones.catalogoDeModelos(id);
|
|
1128
|
-
|
|
1543
|
+
/**
|
|
1544
|
+
* Cada modelo viaja con los niveles de esfuerzo que ADMITE, y se calculan aquí.
|
|
1545
|
+
*
|
|
1546
|
+
* Aquí y no en el cliente porque la tabla vive en `core/esfuerzo.ts` y la frontera
|
|
1547
|
+
* prohíbe que `apps/web/` importe de `src/`. La alternativa era una copia DECLARADA
|
|
1548
|
+
* al otro lado —como la del slug o la del workspace—, y no compensa: aquéllas son una
|
|
1549
|
+
* regla de tres líneas, y ésta es una tabla por familia que además cambia cada vez que
|
|
1550
|
+
* alguien MIDE un modelo nuevo. Dos tablas es un sitio donde una queda vieja sin que
|
|
1551
|
+
* nada lo diga.
|
|
1552
|
+
*
|
|
1553
|
+
* Ausente = ese modelo no admite ninguno, y entonces el control no se pinta.
|
|
1554
|
+
*/
|
|
1555
|
+
catalogos.set(id, {
|
|
1556
|
+
modelos: modelos.map((m) => {
|
|
1557
|
+
const niveles = nivelesDeEsfuerzo(id, m.id);
|
|
1558
|
+
return {
|
|
1559
|
+
id: m.id,
|
|
1560
|
+
...(m.nombre === undefined ? {} : { nombre: m.nombre }),
|
|
1561
|
+
...(niveles === undefined ? {} : { esfuerzos: [...niveles] }),
|
|
1562
|
+
};
|
|
1563
|
+
}),
|
|
1564
|
+
});
|
|
1129
1565
|
}
|
|
1130
1566
|
catch (error) {
|
|
1131
1567
|
// `ErrorCatalogoModelos` es publicable por contrato: nunca lleva la clave ni el
|
|
1132
|
-
// cuerpo remoto (`agent/catalogoModelos.ts`).
|
|
1568
|
+
// cuerpo remoto (`agent/config/catalogoModelos.ts`).
|
|
1133
1569
|
catalogos.set(id, { error: error instanceof Error ? error.message : String(error) });
|
|
1134
1570
|
}
|
|
1135
1571
|
emitirModelos();
|
|
@@ -1470,6 +1906,31 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1470
1906
|
// El cable se muda a la consola del proyecto, como en el alta: sin esto el usuario
|
|
1471
1907
|
// mira un transcript vivo cuyas aprobaciones se rechazan solas al otro lado.
|
|
1472
1908
|
adjuntar();
|
|
1909
|
+
/**
|
|
1910
|
+
* Y el contador se REEMITE, porque el gasto es de UNA conversación.
|
|
1911
|
+
*
|
|
1912
|
+
* `consumo` solo viaja cuando el consumo CAMBIA (`alCambiarConsumo`), y abrir otra
|
|
1913
|
+
* sesión no cambia nada: cambia de quién es la cuenta. Sin esto —medido en la pantalla
|
|
1914
|
+
* del usuario— la caja seguía enseñando los tokens de la conversación anterior hasta la
|
|
1915
|
+
* primera llamada al modelo de la nueva, que puede tardar.
|
|
1916
|
+
*
|
|
1917
|
+
* **No se usa `emitirConsumo()`, y ese es el detalle que costó el primer intento**: esa
|
|
1918
|
+
* función CALLA cuando no consta la cuenta, y al abrir no consta — el `SesionReal` no se
|
|
1919
|
+
* registra hasta que el primer turno lo construye. O sea que justo en el instante que
|
|
1920
|
+
* hay que limpiar, la vía normal no manda nada. Aquí el cero SÍ está medido: una
|
|
1921
|
+
* conversación recién abierta no ha gastado nada, y su tracker nace a cero aunque la
|
|
1922
|
+
* sesión sea vieja. El cliente no pinta un cero, así que el contador desaparece.
|
|
1923
|
+
*
|
|
1924
|
+
* Y si la cuenta SÍ consta —volver al foco de una consola que está corriendo un turno—
|
|
1925
|
+
* se manda la de verdad: mandar cero ahí borraría un contador vivo a mitad de turno.
|
|
1926
|
+
*/
|
|
1927
|
+
const alAbrir = vestibulo.consumoDeSesion();
|
|
1928
|
+
emitir({
|
|
1929
|
+
clase: "consumo",
|
|
1930
|
+
modelo: alAbrir?.modelo ?? SIN_CONSUMO,
|
|
1931
|
+
externo: alAbrir?.externo ?? SIN_CONSUMO,
|
|
1932
|
+
ventana: ventanaDeAhora(alAbrir?.contexto ?? 0),
|
|
1933
|
+
});
|
|
1473
1934
|
// Y la cola se vuelve a mirar: este proyecto queda bloqueado para las tareas —gana la
|
|
1474
1935
|
// persona— y el que estuviera abierto antes acaba de quedar libre.
|
|
1475
1936
|
opciones.revisarTareas?.();
|
|
@@ -1526,6 +1987,72 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1526
1987
|
* Si no hay escritor, se DICE que no se ha guardado en vez de callarlo: un ajuste que
|
|
1527
1988
|
* parece puesto y no lo está es peor que uno que falta.
|
|
1528
1989
|
*/
|
|
1990
|
+
/**
|
|
1991
|
+
* El esfuerzo de la sesión abierta. `nivel` ausente = quitarlo.
|
|
1992
|
+
*
|
|
1993
|
+
* Se encola `/esfuerzo`, que es el MISMO manejador que usa el terminal: la función se
|
|
1994
|
+
* comparte y la sintaxis no se exporta, igual que con `/modelo`. Y **no se guarda ningún
|
|
1995
|
+
* defecto**: a diferencia del modelo, esto es de la sesión y solo de la sesión — un
|
|
1996
|
+
* defecto persistido es otra decisión y no se toma por inercia.
|
|
1997
|
+
*
|
|
1998
|
+
* Sin proyecto abierto no hay sesión a la que aplicárselo, y se DICE en vez de callarlo:
|
|
1999
|
+
* la pastilla solo se pinta con sesión, así que llegar aquí sin ella es un cliente
|
|
2000
|
+
* desincronizado y merece una frase, no un silencio.
|
|
2001
|
+
*/
|
|
2002
|
+
const atenderEsfuerzo = (nivel) => {
|
|
2003
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2004
|
+
if (abierto === undefined) {
|
|
2005
|
+
informar("no hay ninguna sesión abierta a la que fijarle el esfuerzo");
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (nivel === undefined) {
|
|
2009
|
+
abierto.consola.encolar("/esfuerzo ninguno");
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
// Se criba contra el vocabulario ANTES de encolar: lo que llega del cable acaba siendo
|
|
2013
|
+
// un parámetro de una API, y el manejador ya lo rechazaría, pero su rechazo se
|
|
2014
|
+
// imprimiría como una línea de consola en vez de un aviso. Aquí se dice mejor.
|
|
2015
|
+
if (!esEsfuerzo(nivel)) {
|
|
2016
|
+
informar(`«${nivel}» no es un nivel de esfuerzo`);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
abierto.consola.encolar(`/esfuerzo ${nivel}`);
|
|
2020
|
+
};
|
|
2021
|
+
/**
|
|
2022
|
+
* Pregunta a Ollama si el modelo en vigor piensa, y vuelve a emitir cuando conteste.
|
|
2023
|
+
*
|
|
2024
|
+
* Una vez por modelo y por proceso: la respuesta se guarda en `capacidadesVivas`, y sin
|
|
2025
|
+
* el conjunto `capacidadesEnVuelo` cada cliente que se conecta lanzaría la suya. Mismo
|
|
2026
|
+
* molde que `catalogos`.
|
|
2027
|
+
*
|
|
2028
|
+
* Solo Ollama, porque solo Ollama lo necesita: los demás salen de la tabla, que es pura
|
|
2029
|
+
* y síncrona. Y su fallo se TRAGA — lo que no se pudo preguntar deja la pastilla sin
|
|
2030
|
+
* pintar, que es la dirección segura y ya es lo que pasaba antes de que esto existiera.
|
|
2031
|
+
*/
|
|
2032
|
+
const asegurarCapacidades = () => {
|
|
2033
|
+
if (opciones.capacidadesDeModelo === undefined)
|
|
2034
|
+
return;
|
|
2035
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
2036
|
+
if (abierto === undefined)
|
|
2037
|
+
return;
|
|
2038
|
+
const trabajo = resolver(abierto.estadoDeSesion.fuentes).trabajo;
|
|
2039
|
+
if (trabajo.proveedor !== "ollama" && trabajo.proveedor !== "ollama-cloud")
|
|
2040
|
+
return;
|
|
2041
|
+
const clave = `${trabajo.proveedor}/${trabajo.modelo}`;
|
|
2042
|
+
if (capacidadesVivas.has(clave) || capacidadesEnVuelo.has(clave))
|
|
2043
|
+
return;
|
|
2044
|
+
capacidadesEnVuelo.add(clave);
|
|
2045
|
+
void opciones
|
|
2046
|
+
.capacidadesDeModelo(trabajo.proveedor, trabajo.modelo)
|
|
2047
|
+
.then((vivas) => {
|
|
2048
|
+
if (vivas !== undefined) {
|
|
2049
|
+
capacidadesVivas.set(clave, vivas);
|
|
2050
|
+
emitirModelos();
|
|
2051
|
+
}
|
|
2052
|
+
})
|
|
2053
|
+
.catch(() => { })
|
|
2054
|
+
.finally(() => capacidadesEnVuelo.delete(clave));
|
|
2055
|
+
};
|
|
1529
2056
|
const atenderModelo = (id) => {
|
|
1530
2057
|
try {
|
|
1531
2058
|
parsear(id);
|
|
@@ -1761,7 +2288,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1761
2288
|
};
|
|
1762
2289
|
/**
|
|
1763
2290
|
* La sincronización con CloudStudio del proyecto abierto, pedida desde un control
|
|
1764
|
-
* (la banda de arriba de Revisión, y «
|
|
2291
|
+
* (la banda de arriba de Revisión, y «Refrescar»).
|
|
1765
2292
|
*
|
|
1766
2293
|
* **`estado` se mide aquí; `subir` y `bajar` se ENCOLAN**, y la asimetría es deliberada.
|
|
1767
2294
|
* Medir es leer una ref de git que ya está en local (`lecturaDeSync`), así que encolarla
|
|
@@ -1778,7 +2305,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
1778
2305
|
* Y lo que NO se hace es volver a emitir la lectura después de encolar: el servidor no
|
|
1779
2306
|
* sabe cuándo termina una línea de la cola, y un «3 ficheros por subir» recién emitido
|
|
1780
2307
|
* tras pulsar Subir sería una cifra que nadie ha vuelto a medir. La pestaña se refresca
|
|
1781
|
-
* al volver a ella y al pulsar «
|
|
2308
|
+
* al volver a ella y al pulsar «Refrescar», que es cuando de verdad se mira.
|
|
1782
2309
|
*/
|
|
1783
2310
|
const atenderSync = (accion) => {
|
|
1784
2311
|
const abierto = vestibulo.proyectoAbierto();
|
|
@@ -2512,10 +3039,48 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2512
3039
|
// documenta la medida).
|
|
2513
3040
|
// Solo al recién llegado: los demás ya recibieron su saludo al conectar.
|
|
2514
3041
|
sumidero({ clase: "bienvenida", ...(vestibulo.nombre === undefined ? {} : { nombre: vestibulo.nombre }) });
|
|
3042
|
+
/**
|
|
3043
|
+
* **Dos anuncios, y el primero va ANTES de preguntar a CloudStudio.**
|
|
3044
|
+
*
|
|
3045
|
+
* `pasos` se calcula del DISCO (`pasosPendientes`: de dónde vino el modelo y qué entornos
|
|
3046
|
+
* hay registrados), así que en una máquina configurada ya se sabe «no falta nada» sin
|
|
3047
|
+
* tocar la red. Con un solo anuncio detrás de `poblarProyectosSiProcede` —que abre la
|
|
3048
|
+
* sesión MCP— el cliente no podía saberlo hasta que ese viaje terminara, y `App.tsx` lee
|
|
3049
|
+
* un `alta` ausente como «todavía no consta»: enseñaba la pantalla del alta a alguien que
|
|
3050
|
+
* no tiene nada que dar de alta. Medido en la máquina del usuario, con CloudStudio
|
|
3051
|
+
* caliente: el `alta` llegaba a 1436 ms, y con el MCP lento o la red caída esa ventana
|
|
3052
|
+
* crece hasta su tope. Es la misma forma que `MS_DE_TRABAJO_AL_ABRIR`: un dato que se
|
|
3053
|
+
* sabe y no se dice porque va detrás de otro que tarda.
|
|
3054
|
+
*
|
|
3055
|
+
* El segundo anuncio lleva los proyectos, y sigue en un `finally` por lo de siempre: que
|
|
3056
|
+
* `poblarProyectosSiProcede` acabe mal no puede dejar al cliente sin el anuncio. Es el
|
|
3057
|
+
* mismo reparto que ya usaba el cambio de entorno (anunciar, luego preguntar).
|
|
3058
|
+
*/
|
|
3059
|
+
/**
|
|
3060
|
+
* La fase se pone ANTES del primer anuncio y se quita en el `finally`, así que el primer
|
|
3061
|
+
* `alta` sale con ella y el segundo sin ella. Es lo que hace que el Escritorio no entre
|
|
3062
|
+
* hasta estar preparado —lo que el usuario pidió— sin volver a esconder el «no falta
|
|
3063
|
+
* ningún paso», que es lo que el `alta` adelantado vino a arreglar: el cliente sabe
|
|
3064
|
+
* desde el primer mensaje que no hay nada que dar de alta Y que todavía está trayendo
|
|
3065
|
+
* los proyectos, que son dos cosas distintas.
|
|
3066
|
+
*
|
|
3067
|
+
* Con la cuenta pendiente y un entorno ya registrado las dos cosas pasan a la vez, y eso
|
|
3068
|
+
* está bien: la tarjeta pinta el paso de cuenta mientras el MCP trabaja por detrás. Es el
|
|
3069
|
+
* cliente quien decide qué se ve, porque la fase es un dato, no una orden de pintar.
|
|
3070
|
+
*/
|
|
3071
|
+
const aPreparar = entornoAPoblar();
|
|
3072
|
+
preparando = aPreparar === undefined ? undefined : `conectando con ${aPreparar.nombre}…`;
|
|
2515
3073
|
void conducirCuenta()
|
|
2516
3074
|
.catch(contar)
|
|
2517
|
-
.then(() =>
|
|
2518
|
-
.
|
|
3075
|
+
.then(() => anunciarAlta().catch(contar))
|
|
3076
|
+
.then(() => conPlazo(poblarProyectosSiProcede(), MS_DE_PREPARACION))
|
|
3077
|
+
.finally(() => {
|
|
3078
|
+
// Se quita SIEMPRE, también cuando el plazo vence con el MCP todavía colgado: el
|
|
3079
|
+
// lienzo no puede quedarse esperando a quien no contesta. Lo que llegue tarde entra
|
|
3080
|
+
// en el siguiente anuncio, y lo que falló ya viaja en `aviso`.
|
|
3081
|
+
preparando = undefined;
|
|
3082
|
+
void anunciarAlta().catch(contar);
|
|
3083
|
+
});
|
|
2519
3084
|
peticion.on("close", () => {
|
|
2520
3085
|
// Se va ESTE cliente, no «el cliente». La guarda de antes (`enviar !== sumidero`)
|
|
2521
3086
|
// existía porque el `close` de una pestaña recargada puede llegar DESPUÉS de que el
|
|
@@ -2675,6 +3240,61 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2675
3240
|
* - **Ninguna respuesta lleva una ruta de la máquina** ni nada de lo recibido, que es la
|
|
2676
3241
|
* regla de `POST /accion`.
|
|
2677
3242
|
*/
|
|
3243
|
+
/**
|
|
3244
|
+
* `POST /skill?nombre=<fichero.zip>&ambito=<global|proyecto>` — instalar una skill.
|
|
3245
|
+
*
|
|
3246
|
+
* Por HTTP y no por el cable, con el mismo molde que `POST /adjunto`: esto son bytes y el
|
|
3247
|
+
* cable lleva JSON. `Host`, `Origin` y token los comprueba `servidor.ts` antes de llegar
|
|
3248
|
+
* aquí, igual que a todas las rutas. Lo propio de ésta:
|
|
3249
|
+
*
|
|
3250
|
+
* - **El `nombre` de la query es el del FICHERO que subió la persona, y solo se usa como
|
|
3251
|
+
* nombre de respaldo** cuando el zip no trae una carpeta de la que sacarlo
|
|
3252
|
+
* (`core/zipDeSkill.ts`). No se compone ninguna ruta con él sin pasar por la regla del
|
|
3253
|
+
* slug, que es la misma de un subagente.
|
|
3254
|
+
* - **El cuerpo se lee con el tope del ZIP y se corta ahí** (413), sin acumular. Y el otro
|
|
3255
|
+
* tope, el de lo DESCOMPRIMIDO, lo pone `planDeInstalacion`: éste no lo ve, que es
|
|
3256
|
+
* justamente lo que un zip bomba aprovecha.
|
|
3257
|
+
* - **Ninguna respuesta lleva una ruta de la máquina ni nada de lo recibido**, que es la
|
|
3258
|
+
* regla de `POST /accion` — y aquí lo recibido es un fichero de alguien.
|
|
3259
|
+
*/
|
|
3260
|
+
servidor.registrarRuta("POST", RUTA_SKILL, async (peticion, respuesta) => {
|
|
3261
|
+
const responder = (codigo, texto) => {
|
|
3262
|
+
respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
|
|
3263
|
+
respuesta.end(texto);
|
|
3264
|
+
};
|
|
3265
|
+
const query = new URLSearchParams((peticion.url ?? "").split("?")[1] ?? "");
|
|
3266
|
+
const nombre = query.get("nombre");
|
|
3267
|
+
const ambito = query.get("ambito") === "proyecto" ? "proyecto" : "global";
|
|
3268
|
+
if (nombre === null || nombre === "") {
|
|
3269
|
+
responder(400, "falta «nombre»");
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
const abierto = vestibulo.proyectoAbierto();
|
|
3273
|
+
if (ambito === "proyecto" && abierto === undefined) {
|
|
3274
|
+
responder(409, "no hay ningún proyecto abierto: esa skill solo se puede instalar como global");
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
let datos;
|
|
3278
|
+
try {
|
|
3279
|
+
datos = await leerCuerpoCrudo(peticion, TOPE_DEL_ZIP);
|
|
3280
|
+
}
|
|
3281
|
+
catch {
|
|
3282
|
+
responder(413, "el .zip es demasiado grande");
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const instalada = instalarSkillDesdeZip(ambito === "proyecto" ? abierto.raiz : homedir(), new Uint8Array(datos), nombre);
|
|
3286
|
+
if ("error" in instalada) {
|
|
3287
|
+
// El motivo lo escribe el módulo y no lleva ninguna ruta de la máquina ni el nombre de
|
|
3288
|
+
// una entrada del zip (sus tests lo vigilan). 422: el zip llegó entero y no vale.
|
|
3289
|
+
informar(`no se instala la skill: ${instalada.error}`);
|
|
3290
|
+
responder(422, instalada.error);
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
informar(`skill «${instalada.nombre}» instalada en ${ambito}`);
|
|
3294
|
+
emitirSkills();
|
|
3295
|
+
respuesta.writeHead(204);
|
|
3296
|
+
respuesta.end();
|
|
3297
|
+
});
|
|
2678
3298
|
servidor.registrarRuta("POST", RUTA_ADJUNTO, async (peticion, respuesta) => {
|
|
2679
3299
|
const responder = (codigo, texto) => {
|
|
2680
3300
|
respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
|
|
@@ -2808,6 +3428,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2808
3428
|
respuesta.end();
|
|
2809
3429
|
return;
|
|
2810
3430
|
}
|
|
3431
|
+
if (typeof mensaje === "object" &&
|
|
3432
|
+
mensaje !== null &&
|
|
3433
|
+
mensaje.clase === "arrancarEmulador" &&
|
|
3434
|
+
typeof mensaje.avd === "string") {
|
|
3435
|
+
// Se contesta ya: arrancar tarda lo que tarda un emulador y la respuesta del POST no es
|
|
3436
|
+
// el sitio donde se cuenta — eso viaja por el SSE con la foto nueva.
|
|
3437
|
+
void atenderArranqueDeEmulador(mensaje.avd).catch(contar);
|
|
3438
|
+
respuesta.writeHead(204);
|
|
3439
|
+
respuesta.end();
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
2811
3442
|
/**
|
|
2812
3443
|
* ANTES del `recibir` de la consola, y también antes que las demás clases de tarea: por
|
|
2813
3444
|
* aquí no entra nada hacia ningún turno. Ver `atenderMirar`.
|
|
@@ -2875,6 +3506,40 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2875
3506
|
respuesta.end();
|
|
2876
3507
|
return;
|
|
2877
3508
|
}
|
|
3509
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "workspace" && typeof mensaje.ruta === "string") {
|
|
3510
|
+
/**
|
|
3511
|
+
* La regla se aplica AQUÍ además de en la pantalla. El cliente lleva su copia
|
|
3512
|
+
* declarada para poder explicar el no —`informar` no llega al navegador desde el
|
|
3513
|
+
* vestíbulo—, pero la barrera vive en el servidor: una pantalla solo esconde un botón.
|
|
3514
|
+
*
|
|
3515
|
+
* Y se reemite SIEMPRE, se haya escrito o no: con el valor nuevo cuando valió y con el
|
|
3516
|
+
* de antes cuando no, así el campo acaba enseñando lo que hay en disco y no lo que se
|
|
3517
|
+
* tecleó. Un rechazo mudo dejaría la pantalla afirmando un ajuste que no está puesto.
|
|
3518
|
+
*/
|
|
3519
|
+
opciones.guardarWorkspace?.(mensaje.ruta);
|
|
3520
|
+
emitirWorkspace();
|
|
3521
|
+
respuesta.writeHead(204);
|
|
3522
|
+
respuesta.end();
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "elegirCarpeta") {
|
|
3526
|
+
/**
|
|
3527
|
+
* El diálogo lo abre el SISTEMA donde corre la consola, así que esto tarda lo que
|
|
3528
|
+
* tarde una persona. Se contesta 204 en el acto y la carpeta llega después por el SSE:
|
|
3529
|
+
* dejar la petición abierta minutos enteros es lo que este servidor no hace en ningún
|
|
3530
|
+
* otro sitio.
|
|
3531
|
+
*
|
|
3532
|
+
* Se emite SIEMPRE, también sin carpeta: el cliente encendió su «abriendo…» al pedirlo
|
|
3533
|
+
* y sin respuesta se quedaría encendido para siempre después de un simple «cancelar».
|
|
3534
|
+
*/
|
|
3535
|
+
void (async () => {
|
|
3536
|
+
const elegida = await opciones.elegirCarpeta?.(opciones.workspace?.());
|
|
3537
|
+
emitir(elegida === undefined ? { clase: "carpetaElegida" } : { clase: "carpetaElegida", ruta: elegida });
|
|
3538
|
+
})().catch(contar);
|
|
3539
|
+
respuesta.writeHead(204);
|
|
3540
|
+
respuesta.end();
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
2878
3543
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "modelosDeMotor" && typeof mensaje.motor === "string") {
|
|
2879
3544
|
void atenderModelosDeMotor(mensaje.motor).catch(contar);
|
|
2880
3545
|
respuesta.writeHead(204);
|
|
@@ -2912,12 +3577,30 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
2912
3577
|
respuesta.end();
|
|
2913
3578
|
return;
|
|
2914
3579
|
}
|
|
3580
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "esfuerzo") {
|
|
3581
|
+
atenderEsfuerzo(mensaje.nivel);
|
|
3582
|
+
respuesta.writeHead(204);
|
|
3583
|
+
respuesta.end();
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
2915
3586
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "agente") {
|
|
2916
3587
|
atenderAgente(mensaje);
|
|
2917
3588
|
respuesta.writeHead(204);
|
|
2918
3589
|
respuesta.end();
|
|
2919
3590
|
return;
|
|
2920
3591
|
}
|
|
3592
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "skill") {
|
|
3593
|
+
atenderSkill(mensaje);
|
|
3594
|
+
respuesta.writeHead(204);
|
|
3595
|
+
respuesta.end();
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "cuerpoDeSkill") {
|
|
3599
|
+
atenderCuerpoDeSkill(mensaje);
|
|
3600
|
+
respuesta.writeHead(204);
|
|
3601
|
+
respuesta.end();
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
2921
3604
|
if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "proveedor") {
|
|
2922
3605
|
atenderProveedor(mensaje);
|
|
2923
3606
|
respuesta.writeHead(204);
|
|
@@ -3034,6 +3717,19 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3034
3717
|
respuesta.end();
|
|
3035
3718
|
return;
|
|
3036
3719
|
}
|
|
3720
|
+
/**
|
|
3721
|
+
* **Y el acuse del último arranque se OLVIDA.**
|
|
3722
|
+
*
|
|
3723
|
+
* Sin esto se queda pegado para siempre: el usuario vio `pixel8 · apagado` con un
|
|
3724
|
+
* «✓ pixel8 arrancado» al lado, o sea las dos cosas a la vez. El acuse pertenece a la
|
|
3725
|
+
* foto que se tomó justo después de arrancar, no a todas las de después — y ahora que
|
|
3726
|
+
* la sección remide al entrar y al volver, «todas las de después» son muchas.
|
|
3727
|
+
*
|
|
3728
|
+
* Ausente vuelve a significar «no se ha pedido ningún arranque en esta medida», que es
|
|
3729
|
+
* lo que el campo declara. El `finally` de `atenderArranqueDeEmulador` lo pone y se
|
|
3730
|
+
* salta este camino, así que su propia medida sí lo lleva.
|
|
3731
|
+
*/
|
|
3732
|
+
ultimoArranque = undefined;
|
|
3037
3733
|
informeDeDispositivos = undefined;
|
|
3038
3734
|
void atenderDispositivos().catch(contar);
|
|
3039
3735
|
respuesta.writeHead(204);
|
|
@@ -3080,7 +3776,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
|
|
|
3080
3776
|
* Construye el corredor de tareas y la costura que lo conecta al cable.
|
|
3081
3777
|
*
|
|
3082
3778
|
* Extraído de `arrancarConsolaWeb` a su propia función por la MISMA razón que
|
|
3083
|
-
* `backendDeAgente` (`agent/proyecto.ts`): la composición vivía inline dentro de una
|
|
3779
|
+
* `backendDeAgente` (`agent/grafo/proyecto.ts`): la composición vivía inline dentro de una
|
|
3084
3780
|
* función que todos sus tests DOBLAN —`vestibulo`, `crearServidor`, `crearEjecutor`—, así
|
|
3085
3781
|
* que esta costura concreta podía dejar de estar montada con el resto en verde. Aquí
|
|
3086
3782
|
* tiene su propio test que la compone de VERDAD: construye un corredor real
|
|
@@ -3362,12 +4058,71 @@ export function augmentacionCableada(opciones) {
|
|
|
3362
4058
|
export function commitDeTurnoCableado(opciones) {
|
|
3363
4059
|
const commitear = opciones.commitear ?? commitDeTurno;
|
|
3364
4060
|
return async (raiz, mensaje, sesion) => {
|
|
3365
|
-
if (!dentroDelWorkspace(raiz, opciones.base))
|
|
4061
|
+
if (!dentroDelWorkspace(raiz, opciones.base()))
|
|
3366
4062
|
return undefined;
|
|
3367
4063
|
const hecho = await commitear(raiz, mensaje, sesion);
|
|
3368
4064
|
return hecho.via === "fallo" ? `no se pudo commitear el turno: ${hecho.motivo}` : undefined;
|
|
3369
4065
|
};
|
|
3370
4066
|
}
|
|
4067
|
+
/**
|
|
4068
|
+
* La mudanza de una vez de las copias que siguen en el reparto viejo, cableada — y extraída
|
|
4069
|
+
* por el motivo de siempre: compuesta dentro del cierre de `arrancarConsolaWeb`, que todos
|
|
4070
|
+
* sus tests doblan, la regla estaría escrita y no probada.
|
|
4071
|
+
*
|
|
4072
|
+
* Lo que aquí se puede caer sin que TypeScript diga nada son las DOS bases, que no son la
|
|
4073
|
+
* misma cuenta: la VIEJA es `settings.workspace` si estaba puesto —el reparto viejo metía su
|
|
4074
|
+
* `workspace/` en medio fuera cual fuera la base— o `~/.xonecode` a secas; la de AHORA es ese
|
|
4075
|
+
* mismo `settings.workspace` o `~/.xonecode/workspace`. Confundirlas en cualquiera de los dos
|
|
4076
|
+
* sentidos da el mismo resultado: una mudanza que no encuentra nada y no dice nada.
|
|
4077
|
+
*
|
|
4078
|
+
* Y se ESCRIBE en el terminal, nunca por el cable: lo que se cuenta son rutas de la máquina
|
|
4079
|
+
* y nombres de proyecto (`sinRutas`). Corre antes de que exista el vestíbulo, así que ni
|
|
4080
|
+
* siquiera hay cable donde emitirlo.
|
|
4081
|
+
*/
|
|
4082
|
+
export function mudarWorkspaceLegadoCableado(opciones) {
|
|
4083
|
+
const mudar = opciones.mudar ?? mudarWorkspaceLegado;
|
|
4084
|
+
return mudar({
|
|
4085
|
+
legado: opciones.settings.workspace ?? baseDeWorkspaceLegada(),
|
|
4086
|
+
workspace: opciones.settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
4087
|
+
entornos: opciones.settings.entornos.map((e) => e.id),
|
|
4088
|
+
escribir: opciones.escribir,
|
|
4089
|
+
});
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* Las dos mitades del ajuste del workspace, cableadas — y extraídas por el motivo de
|
|
4093
|
+
* siempre: dentro del cierre de `arrancarConsolaWeb`, que todos sus tests doblan, ni una ni
|
|
4094
|
+
* otra estaría probada. Y aquí `guardarWorkspace` LLEVABA la marca: existía en
|
|
4095
|
+
* `agent/config/settingsEnDisco.ts` con un test propio y CERO llamadores de producción, o
|
|
4096
|
+
* sea el ajuste escrito que no hacía nada porque nadie lo había enchufado.
|
|
4097
|
+
*
|
|
4098
|
+
* Tres cosas que se deciden aquí y no en la pantalla:
|
|
4099
|
+
* - **Qué se LEE**: el disco, en cada emisión, no un valor capturado al arrancar. Es la misma
|
|
4100
|
+
* regla que `baseDeWorkspace`, y aquí además cierra el lazo: lo que se acaba de guardar es
|
|
4101
|
+
* lo que se vuelve a emitir.
|
|
4102
|
+
* - **Qué se ACEPTA**: `motivoDeWorkspaceInaceptable` sobre la ruta ya expandida, en el
|
|
4103
|
+
* servidor. El cliente lleva su copia declarada para explicar el no, pero una pantalla
|
|
4104
|
+
* solo esconde un botón.
|
|
4105
|
+
* - **Qué se GUARDA y qué se ENSEÑA**: la ruta ENTERA, nunca un `~/…`. En el fichero porque
|
|
4106
|
+
* la casa cambia de una máquina a otra y un `~` dentro solo significa algo para quien lo
|
|
4107
|
+
* escribió; en la pantalla porque una ruta abreviada no se puede comprobar de un vistazo,
|
|
4108
|
+
* que es para lo que ese campo existe. Se probó a mandarla abreviada —para que el caso
|
|
4109
|
+
* normal no llevara el nombre de la cuenta— y no se sostiene: la cabecera de esa misma
|
|
4110
|
+
* consola ya saluda por el nombre del usuario. El `~` sigue ACEPTÁNDOSE al teclear, que es
|
|
4111
|
+
* comodidad de entrada y otra cosa.
|
|
4112
|
+
*/
|
|
4113
|
+
export function ajusteDeWorkspaceCableado(opciones) {
|
|
4114
|
+
const leer = opciones.leer ?? (() => cargarSettings().settings.workspace);
|
|
4115
|
+
const guardar = opciones.guardar ?? ((ruta) => void guardarWorkspaceEnDisco(undefined, ruta));
|
|
4116
|
+
return {
|
|
4117
|
+
workspace: () => leer() ?? baseDeWorkspacePorOmision(),
|
|
4118
|
+
guardarWorkspace: (ruta) => {
|
|
4119
|
+
const absoluta = expandirConCasa(ruta, opciones.casa);
|
|
4120
|
+
if (motivoDeWorkspaceInaceptable(absoluta) !== undefined)
|
|
4121
|
+
return;
|
|
4122
|
+
guardar(absoluta);
|
|
4123
|
+
},
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
3371
4126
|
export function construirCorredorDeTareasCableado(opciones) {
|
|
3372
4127
|
const disco = opciones.tareasFabrica === undefined ? undefined : opciones.tareasFabrica(opciones.informar);
|
|
3373
4128
|
/**
|
|
@@ -3561,6 +4316,17 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3561
4316
|
if (offline && opciones.guion !== true) {
|
|
3562
4317
|
escribir("este directorio es un proyecto offline: ábrelo con «xonecode --cli»\n");
|
|
3563
4318
|
}
|
|
4319
|
+
/**
|
|
4320
|
+
* La mudanza del reparto viejo, ANTES de que exista el vestíbulo y de que arranque el
|
|
4321
|
+
* corredor de tareas. El orden no es estético: el vestíbulo compone raíces con el reparto
|
|
4322
|
+
* de AHORA y una tarea pendiente lleva su raíz grabada, así que cualquiera de los dos
|
|
4323
|
+
* corriendo antes abriría una carpeta que esta función está a punto de mover.
|
|
4324
|
+
*
|
|
4325
|
+
* Y va aquí y no en el vestíbulo porque no es del vestíbulo: es de la MÁQUINA, pasa una
|
|
4326
|
+
* sola vez, y cuando no quede nadie con el reparto viejo en disco se retira de un tirón
|
|
4327
|
+
* junto con `core/mudanzaDeWorkspace.ts`.
|
|
4328
|
+
*/
|
|
4329
|
+
mudarWorkspaceLegadoCableado({ settings: cargarSettings().settings, escribir });
|
|
3564
4330
|
const arrancar = opciones.crearServidor ?? arrancarServidor;
|
|
3565
4331
|
const servidor = await arrancar({
|
|
3566
4332
|
puerto: opciones.puerto,
|
|
@@ -3664,6 +4430,15 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3664
4430
|
// la raíz que la tarea necesita para poder correr. Ver el comentario de
|
|
3665
4431
|
// `OpcionesDeMontaje.colaDeTareas`.
|
|
3666
4432
|
...opcionesDeTareas,
|
|
4433
|
+
// Dónde se bajan las copias: leerlo y elegirlo. La composición está EXTRAÍDA y probada
|
|
4434
|
+
// (`ajusteDeWorkspaceCableado`) por el patrón de fallo de siempre — y aquí el escritor
|
|
4435
|
+
// ya venía con la marca puesta: existía con su test y sin un solo llamador.
|
|
4436
|
+
...ajusteDeWorkspaceCableado({ casa: homedir() }),
|
|
4437
|
+
// El selector nativo, solo si este sistema tiene uno. En el que no, la opción no se
|
|
4438
|
+
// monta y el botón no llega a existir.
|
|
4439
|
+
...(haySelectorDeCarpeta()
|
|
4440
|
+
? { elegirCarpeta: (desde) => elegirCarpetaEnMaquina(desde === undefined ? {} : { desde }) }
|
|
4441
|
+
: {}),
|
|
3667
4442
|
// Los dos puertos del selector de modelos, con las piezas reales: quién tiene
|
|
3668
4443
|
// credencial (`auth.json` o el entorno, leído desde el cwd) y el catálogo VIVO.
|
|
3669
4444
|
hayCredencial: (proveedor) => hayCredencial(proveedor, opciones.cwd),
|
|
@@ -3673,7 +4448,7 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3673
4448
|
borrarCredencial,
|
|
3674
4449
|
guardarCredencial,
|
|
3675
4450
|
/**
|
|
3676
|
-
* El defecto se GUARDA en el `config.json` global (`agent/configEnDisco.ts#guardarModeloGlobal`),
|
|
4451
|
+
* El defecto se GUARDA en el `config.json` global (`agent/config/configEnDisco.ts#guardarModeloGlobal`),
|
|
3677
4452
|
* que es el último escalón de la precedencia y el único que sobrevive al proceso. Se
|
|
3678
4453
|
* escribe para los TRES papeles porque elegir un modelo en la interfaz es una frase
|
|
3679
4454
|
* sobre el producto y no sobre un papel — el mismo criterio que `/modelo`, que fija los
|
|
@@ -3699,7 +4474,7 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3699
4474
|
cambiosDeSesion,
|
|
3700
4475
|
parcheDeSesion,
|
|
3701
4476
|
// El proyecto tal como lo ve el agente, para la pestaña Ficheros: mismo filtro, misma
|
|
3702
|
-
// barrera de rutas (`agent/arbolDeProyecto.ts`).
|
|
4477
|
+
// barrera de rutas (`agent/grafo/arbolDeProyecto.ts`).
|
|
3703
4478
|
arbolDelProyecto: async (raiz) => arbolDeProyecto(raiz),
|
|
3704
4479
|
leerFichero: leerFicheroDeProyecto,
|
|
3705
4480
|
leerArtefacto: leerArtefactoDeSesion,
|
|
@@ -3714,6 +4489,11 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3714
4489
|
guardarAjustesDeDispositivos: (ajustes) => void guardarDispositivos(undefined, ajustes),
|
|
3715
4490
|
instalarHerramienta: instalarHerramientaDeDispositivos,
|
|
3716
4491
|
verificarDispositivo,
|
|
4492
|
+
// Con la función REAL, y no declarada y sin pasar: es la trampa que este repo ha pagado
|
|
4493
|
+
// nueve veces, y la que el comentario de justo abajo describe para las cuatro del
|
|
4494
|
+
// lanzamiento. `montarRutas` lo trata como ausente si falta, así que olvidarlo aquí
|
|
4495
|
+
// dejaría el botón diciendo «esta ejecución no puede arrancar emuladores» con todo verde.
|
|
4496
|
+
arrancarEmulador: (avd) => arrancarEmulador(avd),
|
|
3717
4497
|
/**
|
|
3718
4498
|
* Las cuatro de «¿se puede lanzar y lánzalo?», compuestas con las funciones REALES.
|
|
3719
4499
|
*
|
|
@@ -3743,6 +4523,14 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3743
4523
|
const modelos = await new CatalogoModelos(undefined, undefined, proveedoresPersonalizados).listar(proveedor);
|
|
3744
4524
|
return modelos.map((m) => ({ id: m.id, ...(m.nombre === undefined ? {} : { nombre: m.nombre }) }));
|
|
3745
4525
|
},
|
|
4526
|
+
/**
|
|
4527
|
+
* Solo Ollama tiene a quién preguntarle esto, y por eso los demás devuelven «no consta»
|
|
4528
|
+
* aquí en vez de que lo decida `asegurarCapacidades`: quien sabe qué proveedores saben
|
|
4529
|
+
* contestar es este cableado, no el servidor.
|
|
4530
|
+
*/
|
|
4531
|
+
capacidadesDeModelo: async (proveedor, modelo) => proveedor === "ollama" || proveedor === "ollama-cloud"
|
|
4532
|
+
? capacidadesDeOllama(modelo, proveedor === "ollama" ? baseUrlDeOllama() : baseUrlDeOllamaCloud())
|
|
4533
|
+
: undefined,
|
|
3746
4534
|
// Se releen de disco en cada consulta, igual que los ajustes de dispositivos: esta
|
|
3747
4535
|
// misma ventana los da de alta, y una lista capturada al montar las rutas se quedaría
|
|
3748
4536
|
// vieja hasta reiniciar el proceso.
|
|
@@ -3750,7 +4538,7 @@ export async function arrancarConsolaWeb(opciones) {
|
|
|
3750
4538
|
guardarProveedor: (declarado) => guardarProveedorPersonalizado(declarado),
|
|
3751
4539
|
borrarProveedor: (slug) => borrarProveedorPersonalizado(slug),
|
|
3752
4540
|
/**
|
|
3753
|
-
* El AUMENTADOR de la ventana de crear una tarea (`agent/aumentador.ts`), con el papel
|
|
4541
|
+
* El AUMENTADOR de la ventana de crear una tarea (`agent/tareas/aumentador.ts`), con el papel
|
|
3754
4542
|
* `trabajo` — es una tarea de redacción, no una clasificación.
|
|
3755
4543
|
*
|
|
3756
4544
|
* Tres cosas que se deciden aquí:
|
|
@@ -3885,7 +4673,7 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
|
3885
4673
|
// Se resuelve UNA vez, aquí, y no en cada `anunciarAlta`: `git config`/`os.userInfo`
|
|
3886
4674
|
// no cambian a media conexión, y repetir el subproceso en cada anuncio del alta sería
|
|
3887
4675
|
// gastar sin motivo. Nunca viaja hacia CloudStudio ni hacia ningún acto —
|
|
3888
|
-
// `agent/persona.ts` lo documenta—.
|
|
4676
|
+
// `agent/config/persona.ts` lo documenta—.
|
|
3889
4677
|
nombre: nombreDePersona(opciones.cwd),
|
|
3890
4678
|
catalogoModelos: new CatalogoModelos(),
|
|
3891
4679
|
guardarCredencial,
|
|
@@ -3898,7 +4686,7 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
|
3898
4686
|
guardarModeloGlobal,
|
|
3899
4687
|
guardarConfigDeProyecto: escribirProyectoEnDisco,
|
|
3900
4688
|
// El «antes» de cada sesión: se fotografía al abrir el proyecto y se nombra cuando la
|
|
3901
|
-
// sesión tiene id. Ver `agent/sesionGit.ts` para por qué es una ref y no un tag.
|
|
4689
|
+
// sesión tiene id. Ver `agent/sesiones/sesionGit.ts` para por qué es una ref y no un tag.
|
|
3902
4690
|
marcarSesion: fotoDeApertura,
|
|
3903
4691
|
// Lo que ya había sin commitear al abrir, para poder decirlo. Comparte el hueco
|
|
3904
4692
|
// declarado de `marcarSesion`: esta composición vive en un cierre que `vestibuloReal`
|
|
@@ -3909,14 +4697,25 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
|
|
|
3909
4697
|
// El commit del turno, con el sello de la sesión. La composición está EXTRAÍDA y probada
|
|
3910
4698
|
// (`commitDeTurnoCableado`): el argumento que se cae en una lambda escrita a mano es
|
|
3911
4699
|
// justo el que sostiene la atribución de Revisión.
|
|
3912
|
-
commitearTurno: commitDeTurnoCableado({
|
|
4700
|
+
commitearTurno: commitDeTurnoCableado({
|
|
4701
|
+
base: () => cargarSettings().settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
4702
|
+
}),
|
|
3913
4703
|
olvidarMarcaDeSesion: olvidarSesion,
|
|
3914
4704
|
// La memoria del agente por hilo. `historica` deja de ser «se reabrió» para ser «no hay
|
|
3915
4705
|
// checkpoint que cargar», y borrar una sesión se lleva también su checkpoint.
|
|
3916
4706
|
hayMemoriaDeHilo: async (raiz, hilo) => hayCheckpoint(crearCheckpointerDeProyecto(raiz), hilo),
|
|
3917
4707
|
olvidarMemoriaDeHilo: async (raiz, hilo) => olvidarHilo(crearCheckpointerDeProyecto(raiz), hilo),
|
|
3918
4708
|
entornos: settings.entornos,
|
|
3919
|
-
|
|
4709
|
+
/**
|
|
4710
|
+
* Dónde se bajan las copias, RELEÍDO en cada uso y no resuelto aquí.
|
|
4711
|
+
*
|
|
4712
|
+
* `settings.workspace` se cambia desde Ajustes con la consola en marcha, así que un
|
|
4713
|
+
* valor capturado al arrancar dejaría el resto del proceso bajando al sitio de antes
|
|
4714
|
+
* mientras la pantalla enseña el nuevo — el ajuste escrito que no hace nada. Es lo
|
|
4715
|
+
* mismo que ya hacen `sinAprobacion` y el tope de concurrencia, y por lo mismo: releer
|
|
4716
|
+
* un JSON pequeño no cuesta nada al lado de equivocarse de carpeta.
|
|
4717
|
+
*/
|
|
4718
|
+
baseDeWorkspace: () => cargarSettings().settings.workspace ?? baseDeWorkspacePorOmision(),
|
|
3920
4719
|
// La URL de la web para que la página del callback devuelva AQUÍ y no diga «vuelve a
|
|
3921
4720
|
// la terminal», que en un navegador es falso.
|
|
3922
4721
|
...conexionDeVestibulo(servidor.url),
|