xonecode 0.5.0 → 0.6.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.
Files changed (266) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +32 -0
  3. package/apps/web/dist/assets/index-DBZTodio.js +477 -0
  4. package/apps/web/dist/assets/{index-BB3SamhR.css → index-rqecK6s2.css} +1 -1
  5. package/apps/web/dist/assets/{php-D_2--9WK.js → php-BBij3Lhc.js} +1 -1
  6. package/apps/web/dist/assets/{ruby-DSHSDbk1.js → ruby-Dq2YEaLo.js} +1 -1
  7. package/apps/web/dist/index.html +2 -2
  8. package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -0
  9. package/dist/agent/{cloudstudioMcp.js → cloudstudio/cloudstudioMcp.js} +5 -5
  10. package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -0
  11. package/dist/agent/{descarga.js → cloudstudio/descarga.js} +3 -3
  12. package/dist/agent/cloudstudio/descarga.js.map +1 -0
  13. package/dist/agent/cloudstudio/manifiesto.js.map +1 -0
  14. package/dist/agent/{subida.js → cloudstudio/subida.js} +3 -3
  15. package/dist/agent/cloudstudio/subida.js.map +1 -0
  16. package/dist/agent/{zip.js → cloudstudio/zip.js} +1 -1
  17. package/dist/agent/cloudstudio/zip.js.map +1 -0
  18. package/dist/agent/{authEnDisco.js → config/authEnDisco.js} +3 -3
  19. package/dist/agent/config/authEnDisco.js.map +1 -0
  20. package/dist/agent/{catalogoModelos.js → config/catalogoModelos.js} +2 -1
  21. package/dist/agent/config/catalogoModelos.js.map +1 -0
  22. package/dist/agent/{configEnDisco.js → config/configEnDisco.js} +6 -6
  23. package/dist/agent/config/configEnDisco.js.map +1 -0
  24. package/dist/agent/{crearProyecto.js → config/crearProyecto.js} +2 -2
  25. package/dist/agent/config/crearProyecto.js.map +1 -0
  26. package/dist/agent/config/entorno.js.map +1 -0
  27. package/dist/agent/config/gemini.js.map +1 -0
  28. package/dist/agent/{modelos.js → config/modelos.js} +17 -3
  29. package/dist/agent/config/modelos.js.map +1 -0
  30. package/dist/agent/{modelosDeMotor.js → config/modelosDeMotor.js} +1 -1
  31. package/dist/agent/config/modelosDeMotor.js.map +1 -0
  32. package/dist/agent/{persona.js → config/persona.js} +1 -1
  33. package/dist/agent/config/persona.js.map +1 -0
  34. package/dist/agent/{settingsEnDisco.js → config/settingsEnDisco.js} +2 -2
  35. package/dist/agent/config/settingsEnDisco.js.map +1 -0
  36. package/dist/agent/{versionEnDisco.js → config/versionEnDisco.js} +8 -4
  37. package/dist/agent/config/versionEnDisco.js.map +1 -0
  38. package/dist/agent/dispositivos/arranqueDeEmulador.js +146 -0
  39. package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -0
  40. package/dist/agent/{dispositivosEnMaquina.js → dispositivos/dispositivosEnMaquina.js} +93 -6
  41. package/dist/agent/dispositivos/dispositivosEnMaquina.js.map +1 -0
  42. package/dist/agent/dispositivos/hotswap.js.map +1 -0
  43. package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -0
  44. package/dist/agent/{lanzamientoEnMaquina.js → dispositivos/lanzamientoEnMaquina.js} +1 -1
  45. package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -0
  46. package/dist/agent/{paqueteDelProyecto.js → dispositivos/paqueteDelProyecto.js} +5 -5
  47. package/dist/agent/dispositivos/paqueteDelProyecto.js.map +1 -0
  48. package/dist/agent/dispositivos/procesosEnMaquina.js.map +1 -0
  49. package/dist/agent/{arbolDeProyecto.js → grafo/arbolDeProyecto.js} +2 -2
  50. package/dist/agent/grafo/arbolDeProyecto.js.map +1 -0
  51. package/dist/agent/{artefactosEnDisco.js → grafo/artefactosEnDisco.js} +2 -2
  52. package/dist/agent/grafo/artefactosEnDisco.js.map +1 -0
  53. package/dist/agent/{busquedaRegex.js → grafo/busquedaRegex.js} +41 -1
  54. package/dist/agent/grafo/busquedaRegex.js.map +1 -0
  55. package/dist/agent/grafo/excluirTools.js +82 -0
  56. package/dist/agent/grafo/excluirTools.js.map +1 -0
  57. package/dist/agent/{memoriaDeProyecto.js → grafo/memoriaDeProyecto.js} +1 -1
  58. package/dist/agent/grafo/memoriaDeProyecto.js.map +1 -0
  59. package/dist/agent/grafo/navegacionXone.js +198 -0
  60. package/dist/agent/grafo/navegacionXone.js.map +1 -0
  61. package/dist/agent/{perfiles.js → grafo/perfiles.js} +51 -8
  62. package/dist/agent/grafo/perfiles.js.map +1 -0
  63. package/dist/agent/{proyecto.js → grafo/proyecto.js} +207 -24
  64. package/dist/agent/grafo/proyecto.js.map +1 -0
  65. package/dist/agent/grafo/skills.js +393 -0
  66. package/dist/agent/grafo/skills.js.map +1 -0
  67. package/dist/agent/{xoneAgent.js → grafo/xoneAgent.js} +196 -67
  68. package/dist/agent/grafo/xoneAgent.js.map +1 -0
  69. package/dist/agent/navegacion/indiceEnDisco.js +21 -0
  70. package/dist/agent/navegacion/indiceEnDisco.js.map +1 -0
  71. package/dist/agent/navegacion/modeloDeProyecto.js +228 -0
  72. package/dist/agent/navegacion/modeloDeProyecto.js.map +1 -0
  73. package/dist/agent/raizDelPaquete.js +56 -0
  74. package/dist/agent/raizDelPaquete.js.map +1 -0
  75. package/dist/agent/{checkpointer.js → sesiones/checkpointer.js} +2 -2
  76. package/dist/agent/sesiones/checkpointer.js.map +1 -0
  77. package/dist/agent/sesiones/git.js.map +1 -0
  78. package/dist/agent/{gitSync.js → sesiones/gitSync.js} +3 -3
  79. package/dist/agent/sesiones/gitSync.js.map +1 -0
  80. package/dist/agent/{sesionGit.js → sesiones/sesionGit.js} +2 -2
  81. package/dist/agent/sesiones/sesionGit.js.map +1 -0
  82. package/dist/agent/{agentesEnDisco.js → subagentes/agentesEnDisco.js} +304 -86
  83. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -0
  84. package/dist/agent/subagentes/consumoExterno.js.map +1 -0
  85. package/dist/agent/subagentes/escrituraDeCodex.js.map +1 -0
  86. package/dist/agent/{escrituraDeOpencode.js → subagentes/escrituraDeOpencode.js} +1 -1
  87. package/dist/agent/subagentes/escrituraDeOpencode.js.map +1 -0
  88. package/dist/agent/{escrituraExterna.js → subagentes/escrituraExterna.js} +6 -6
  89. package/dist/agent/subagentes/escrituraExterna.js.map +1 -0
  90. package/dist/agent/subagentes/subagenteCodex.js.map +1 -0
  91. package/dist/agent/subagentes/subagenteExterno.js.map +1 -0
  92. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -0
  93. package/dist/agent/{aumentador.js → tareas/aumentador.js} +2 -2
  94. package/dist/agent/tareas/aumentador.js.map +1 -0
  95. package/dist/agent/{juezDeTarea.js → tareas/juezDeTarea.js} +5 -5
  96. package/dist/agent/tareas/juezDeTarea.js.map +1 -0
  97. package/dist/agent/{tareasEnDisco.js → tareas/tareasEnDisco.js} +4 -4
  98. package/dist/agent/tareas/tareasEnDisco.js.map +1 -0
  99. package/dist/agent/turno/diagnosticoDeTools.js.map +1 -0
  100. package/dist/agent/{guionizado.js → turno/guionizado.js} +1 -1
  101. package/dist/agent/turno/guionizado.js.map +1 -0
  102. package/dist/agent/turno/informeDeTraza.js +222 -0
  103. package/dist/agent/turno/informeDeTraza.js.map +1 -0
  104. package/dist/agent/turno/inspectorDePrompt.js +205 -0
  105. package/dist/agent/turno/inspectorDePrompt.js.map +1 -0
  106. package/dist/agent/{instantanea.js → turno/instantanea.js} +7 -2
  107. package/dist/agent/turno/instantanea.js.map +1 -0
  108. package/dist/agent/{interrupts.js → turno/interrupts.js} +2 -2
  109. package/dist/agent/turno/interrupts.js.map +1 -0
  110. package/dist/agent/turno/mensajes.js.map +1 -0
  111. package/dist/agent/turno/normalizar.js.map +1 -0
  112. package/dist/agent/{puente.js → turno/puente.js} +47 -3
  113. package/dist/agent/turno/puente.js.map +1 -0
  114. package/dist/agent/turno/resumenDeContexto.js +176 -0
  115. package/dist/agent/turno/resumenDeContexto.js.map +1 -0
  116. package/dist/agent/{resumenDeTool.js → turno/resumenDeTool.js} +15 -0
  117. package/dist/agent/turno/resumenDeTool.js.map +1 -0
  118. package/dist/agent/turno/textoDeTool.js.map +1 -0
  119. package/dist/agent/{turnoReal.js → turno/turnoReal.js} +38 -14
  120. package/dist/agent/turno/turnoReal.js.map +1 -0
  121. package/dist/agent/turno/verificador.js.map +1 -0
  122. package/dist/cli/config.js +1 -1
  123. package/dist/cli/config.js.map +1 -1
  124. package/dist/cli/consola.js +9 -9
  125. package/dist/cli/consola.js.map +1 -1
  126. package/dist/cli/describe.js +2 -2
  127. package/dist/cli/describe.js.map +1 -1
  128. package/dist/cli/doctor.js +1 -1
  129. package/dist/cli/doctor.js.map +1 -1
  130. package/dist/cli/fuentesDeDisco.js +29 -0
  131. package/dist/cli/fuentesDeDisco.js.map +1 -0
  132. package/dist/cli/main.js +37 -28
  133. package/dist/cli/main.js.map +1 -1
  134. package/dist/cli/run.js +32 -10
  135. package/dist/cli/run.js.map +1 -1
  136. package/dist/cli/traza.js +48 -0
  137. package/dist/cli/traza.js.map +1 -0
  138. package/dist/cli/tui/correrTui.js +4 -4
  139. package/dist/cli/tui/correrTui.js.map +1 -1
  140. package/dist/cli/verify.js +1 -1
  141. package/dist/cli/verify.js.map +1 -1
  142. package/dist/core/actos.js +1 -1
  143. package/dist/core/adjuntos.js +2 -2
  144. package/dist/core/agentes.js +145 -7
  145. package/dist/core/agentes.js.map +1 -1
  146. package/dist/core/artefactos.js +4 -4
  147. package/dist/core/config.js +1 -1
  148. package/dist/core/contextos.js +12 -1
  149. package/dist/core/contextos.js.map +1 -1
  150. package/dist/core/descargas.js +1 -1
  151. package/dist/core/dispositivos.js +53 -3
  152. package/dist/core/dispositivos.js.map +1 -1
  153. package/dist/core/esqueleto.js +1 -1
  154. package/dist/core/modelos.js +16 -3
  155. package/dist/core/modelos.js.map +1 -1
  156. package/dist/core/navegacion.js +134 -0
  157. package/dist/core/navegacion.js.map +1 -0
  158. package/dist/core/notify.js +5 -0
  159. package/dist/core/notify.js.map +1 -1
  160. package/dist/core/planDeSubida.js +1 -1
  161. package/dist/core/settings.js +2 -2
  162. package/dist/core/shellDeAgente.js +99 -0
  163. package/dist/core/shellDeAgente.js.map +1 -0
  164. package/dist/core/skills.js +158 -0
  165. package/dist/core/skills.js.map +1 -0
  166. package/dist/core/zipDeSkill.js +107 -0
  167. package/dist/core/zipDeSkill.js.map +1 -0
  168. package/dist/vendor/sqliteSaver.js +389 -0
  169. package/dist/vendor/sqliteSaver.js.map +1 -0
  170. package/dist/web/servidor/arranque.js +531 -47
  171. package/dist/web/servidor/arranque.js.map +1 -1
  172. package/dist/web/servidor/corredorDeTareas.js +3 -3
  173. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  174. package/dist/web/servidor/sesiones.js +4 -4
  175. package/dist/web/servidor/transporte.js +1 -1
  176. package/dist/web/servidor/transporte.js.map +1 -1
  177. package/dist/web/servidor/vestibulo.js +7 -7
  178. package/dist/web/servidor/vestibulo.js.map +1 -1
  179. package/package.json +9 -5
  180. package/skills/archify/SKILL.md +25 -2
  181. package/skills/artifacts-builder/SKILL.md +23 -0
  182. package/skills/xone-hotswap/SKILL.md +74 -0
  183. package/skills/xone-hotswap/scripts/xone-arrancar-ios +85 -0
  184. package/skills/xone-hotswap/scripts/xone-desplegar-android +411 -0
  185. package/skills/xone-hotswap/scripts/xone-hotswap +171 -0
  186. package/skills/xone-hotswap/scripts/xone-log-android +102 -0
  187. package/skills/xone-hotswap/scripts/xone-reiniciar-android +104 -0
  188. package/apps/web/dist/assets/index-CFYAoHdw.js +0 -477
  189. package/dist/agent/agentesEnDisco.js.map +0 -1
  190. package/dist/agent/arbolDeProyecto.js.map +0 -1
  191. package/dist/agent/artefactosEnDisco.js.map +0 -1
  192. package/dist/agent/aumentador.js.map +0 -1
  193. package/dist/agent/authEnDisco.js.map +0 -1
  194. package/dist/agent/busquedaRegex.js.map +0 -1
  195. package/dist/agent/catalogoModelos.js.map +0 -1
  196. package/dist/agent/checkpointer.js.map +0 -1
  197. package/dist/agent/cloudstudioClient.js.map +0 -1
  198. package/dist/agent/cloudstudioMcp.js.map +0 -1
  199. package/dist/agent/configEnDisco.js.map +0 -1
  200. package/dist/agent/consumoExterno.js.map +0 -1
  201. package/dist/agent/crearProyecto.js.map +0 -1
  202. package/dist/agent/descarga.js.map +0 -1
  203. package/dist/agent/diagnosticoDeTools.js.map +0 -1
  204. package/dist/agent/dispositivosEnMaquina.js.map +0 -1
  205. package/dist/agent/entorno.js.map +0 -1
  206. package/dist/agent/escrituraDeCodex.js.map +0 -1
  207. package/dist/agent/escrituraDeOpencode.js.map +0 -1
  208. package/dist/agent/escrituraExterna.js.map +0 -1
  209. package/dist/agent/gemini.js.map +0 -1
  210. package/dist/agent/git.js.map +0 -1
  211. package/dist/agent/gitSync.js.map +0 -1
  212. package/dist/agent/guionizado.js.map +0 -1
  213. package/dist/agent/hotswap.js.map +0 -1
  214. package/dist/agent/instalacionEnMaquina.js.map +0 -1
  215. package/dist/agent/instantanea.js.map +0 -1
  216. package/dist/agent/interrupts.js.map +0 -1
  217. package/dist/agent/juezDeTarea.js.map +0 -1
  218. package/dist/agent/lanzamientoEnMaquina.js.map +0 -1
  219. package/dist/agent/manifiesto.js.map +0 -1
  220. package/dist/agent/memoriaDeProyecto.js.map +0 -1
  221. package/dist/agent/mensajes.js.map +0 -1
  222. package/dist/agent/modelos.js.map +0 -1
  223. package/dist/agent/modelosDeMotor.js.map +0 -1
  224. package/dist/agent/normalizar.js.map +0 -1
  225. package/dist/agent/paqueteDelProyecto.js.map +0 -1
  226. package/dist/agent/perfiles.js.map +0 -1
  227. package/dist/agent/persona.js.map +0 -1
  228. package/dist/agent/procesosEnMaquina.js.map +0 -1
  229. package/dist/agent/proyecto.js.map +0 -1
  230. package/dist/agent/puente.js.map +0 -1
  231. package/dist/agent/resumenDeContexto.js +0 -30
  232. package/dist/agent/resumenDeContexto.js.map +0 -1
  233. package/dist/agent/resumenDeTool.js.map +0 -1
  234. package/dist/agent/sesionGit.js.map +0 -1
  235. package/dist/agent/settingsEnDisco.js.map +0 -1
  236. package/dist/agent/skills.js +0 -65
  237. package/dist/agent/skills.js.map +0 -1
  238. package/dist/agent/subagenteCodex.js.map +0 -1
  239. package/dist/agent/subagenteExterno.js.map +0 -1
  240. package/dist/agent/subagenteOpencode.js.map +0 -1
  241. package/dist/agent/subida.js.map +0 -1
  242. package/dist/agent/tareasEnDisco.js.map +0 -1
  243. package/dist/agent/textoDeTool.js.map +0 -1
  244. package/dist/agent/turnoReal.js.map +0 -1
  245. package/dist/agent/verificador.js.map +0 -1
  246. package/dist/agent/versionEnDisco.js.map +0 -1
  247. package/dist/agent/xoneAgent.js.map +0 -1
  248. package/dist/agent/zip.js.map +0 -1
  249. /package/dist/agent/{cloudstudioClient.js → cloudstudio/cloudstudioClient.js} +0 -0
  250. /package/dist/agent/{manifiesto.js → cloudstudio/manifiesto.js} +0 -0
  251. /package/dist/agent/{entorno.js → config/entorno.js} +0 -0
  252. /package/dist/agent/{gemini.js → config/gemini.js} +0 -0
  253. /package/dist/agent/{hotswap.js → dispositivos/hotswap.js} +0 -0
  254. /package/dist/agent/{instalacionEnMaquina.js → dispositivos/instalacionEnMaquina.js} +0 -0
  255. /package/dist/agent/{procesosEnMaquina.js → dispositivos/procesosEnMaquina.js} +0 -0
  256. /package/dist/agent/{git.js → sesiones/git.js} +0 -0
  257. /package/dist/agent/{consumoExterno.js → subagentes/consumoExterno.js} +0 -0
  258. /package/dist/agent/{escrituraDeCodex.js → subagentes/escrituraDeCodex.js} +0 -0
  259. /package/dist/agent/{subagenteCodex.js → subagentes/subagenteCodex.js} +0 -0
  260. /package/dist/agent/{subagenteExterno.js → subagentes/subagenteExterno.js} +0 -0
  261. /package/dist/agent/{subagenteOpencode.js → subagentes/subagenteOpencode.js} +0 -0
  262. /package/dist/agent/{diagnosticoDeTools.js → turno/diagnosticoDeTools.js} +0 -0
  263. /package/dist/agent/{mensajes.js → turno/mensajes.js} +0 -0
  264. /package/dist/agent/{normalizar.js → turno/normalizar.js} +0 -0
  265. /package/dist/agent/{textoDeTool.js → turno/textoDeTool.js} +0 -0
  266. /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 { borrarAgente, cargarAgentes, guardarAgente } from "../../agent/agentesEnDisco.js";
38
- import { detectarDispositivos, frameworkEnDispositivo } from "../../agent/dispositivosEnMaquina.js";
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,41 +48,43 @@ 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 { correrPasoDeReceta } from "../../agent/instalacionEnMaquina.js";
53
- import { modelosDeMotor } from "../../agent/modelosDeMotor.js";
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";
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, guardarDispositivos, guardarEntorno as guardarEntornoEnDisco, } from "../../agent/config/settingsEnDisco.js";
62
66
  import { dentroDelWorkspace, seAplicaSinAprobacion } from "../../core/settings.js";
63
- import { cloudstudioDelProyecto } from "../../agent/configEnDisco.js";
64
- import { abrirEnSistema } from "../../agent/cloudstudioMcp.js";
65
- import { nombreDePersona } from "../../agent/persona.js";
66
- import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "../../agent/sesionGit.js";
67
- import { commitDeTurno, cambiosPendientes, trabajoSinCommitear } from "../../agent/gitSync.js";
67
+ import { cloudstudioDelProyecto } from "../../agent/config/configEnDisco.js";
68
+ import { abrirEnSistema } from "../../agent/cloudstudio/cloudstudioMcp.js";
69
+ import { nombreDePersona } from "../../agent/config/persona.js";
70
+ import { cambiosDeSesion, fotoDeApertura, olvidarSesion, parcheDeSesion } from "../../agent/sesiones/sesionGit.js";
71
+ import { commitDeTurno, cambiosPendientes, trabajoSinCommitear } from "../../agent/sesiones/gitSync.js";
68
72
  import { marcarTareaDeSesion, sembrarConsumosPendientes } from "./sesiones.js";
69
73
  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";
74
+ import { arbolDeProyecto, leerFicheroDeProyecto, motivoDeRutaInaceptable, } from "../../agent/grafo/arbolDeProyecto.js";
75
+ import { leerArtefactoCrudo, leerArtefactoDeSesion, } from "../../agent/grafo/artefactosEnDisco.js";
76
+ import { CatalogoModelos } from "../../agent/config/catalogoModelos.js";
77
+ import { Modelos } from "../../agent/config/modelos.js";
78
+ import { crearJuezDeTarea, invocarConModelos } from "../../agent/tareas/juezDeTarea.js";
79
+ import { SIN_CONSUMO } from "../../agent/subagentes/consumoExterno.js";
75
80
  import { AumentadorGuionizado } from "../../core/ports.js";
76
81
  import { arrancarServidor } from "./servidor.js";
77
82
  import { consolaParaTarea, crearCorredorDeTareas, revisionConGit, } from "./corredorDeTareas.js";
78
83
  import { CONCURRENCIA_POR_OMISION, conEstado, darPorBuenaAMano, tituloDeTarea, } from "../../core/tareas.js";
79
- import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareasEnDisco.js";
84
+ import { aplicarFeedback, TOPE_DE_ADJUNTO } from "../../agent/tareas/tareasEnDisco.js";
80
85
  import { nombreDeAdjuntoAceptable } from "../../core/adjuntos.js";
81
- import { rutaMemoriaDeProyecto } from "../../agent/memoriaDeProyecto.js";
82
- import { crearAumentador, invocarParaAumentar } from "../../agent/aumentador.js";
86
+ import { rutaMemoriaDeProyecto } from "../../agent/grafo/memoriaDeProyecto.js";
87
+ import { crearAumentador, invocarParaAumentar } from "../../agent/tareas/aumentador.js";
83
88
  import { baseDeWorkspacePorOmision, conexionDeVestibulo, crearVestibulo, escribirProyectoEnDisco, esProyectoEnDisco, } from "./vestibulo.js";
84
89
  // Valor y no tipo: la traducción de una `Tarea` a lo que viaja vive JUNTO al tipo que
85
90
  // produce y no aquí. Estuvo en este cierre, y ahí se cayó `veredicto` sin que nada se
@@ -97,6 +102,24 @@ import { filaDeTarea } from "./transporte.js";
97
102
  * definición de que el aviso frena, que es justo lo que su propia regla prohíbe.
98
103
  */
99
104
  export const MS_DE_TRABAJO_AL_ABRIR = 2000;
105
+ /**
106
+ * Cuánto se espera al arranque antes de entrar igual.
107
+ *
108
+ * Lo que se prepara es la sesión MCP y la lista de proyectos del primer entorno registrado,
109
+ * y el usuario pidió no entrar al Escritorio hasta tenerlo: entrar antes significa un
110
+ * Escritorio vacío que se rellena delante. Pero esperar SIN plazo es lo que convierte una
111
+ * espera en un cuelgue — medido con un MCP que descarta paquetes, el lienzo se quedaba para
112
+ * siempre, y eso es peor que lo que veníamos a arreglar.
113
+ *
114
+ * Generoso a propósito: una sesión MCP fría lleva descubrimiento OAuth y handshake, y
115
+ * cortarla pronto haría entrar en vacío justo a quien tiene la red lenta, que es a quien más
116
+ * le duele. Y como el lienzo DICE lo que está haciendo (`preparando`), la espera se lee como
117
+ * trabajo y no como una pantalla muerta.
118
+ *
119
+ * Igual que `MS_DE_TRABAJO_AL_ABRIR`, lo que se acota es la ESPERA y no el trabajo: la
120
+ * promesa sigue viva y lo que llegue tarde sale en el siguiente anuncio.
121
+ */
122
+ export const MS_DE_PREPARACION = 10_000;
100
123
  export const RUTA_EVENTOS = "/eventos";
101
124
  export const RUTA_ACCION = "/accion";
102
125
  /**
@@ -109,6 +132,13 @@ export const RUTA_ACCION = "/accion";
109
132
  * el contrato de las skills que los escriben es «autocontenido».
110
133
  */
111
134
  export const RUTA_ARTEFACTO = "/artefacto";
135
+ /**
136
+ * `POST /skill?nombre=<fichero.zip>&ambito=<global|proyecto>` — los BYTES de un `.zip`.
137
+ *
138
+ * Por HTTP y no por el cable, que lleva JSON: la misma razón y el mismo molde que
139
+ * `POST /adjunto`. Una ruta EXACTA, porque el servidor casa por ruta exacta.
140
+ */
141
+ export const RUTA_SKILL = "/skill";
112
142
  /**
113
143
  * `POST /adjunto?tarea=<id>&nombre=<fichero>` — los BYTES de un adjunto.
114
144
  *
@@ -245,6 +275,14 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
245
275
  * en la Trayectoria —la otra pestaña—, y el wizard repintaba el mismo paso sin decir nada.
246
276
  */
247
277
  let aviso;
278
+ /**
279
+ * Qué está preparando el arranque, mientras lo prepara. `undefined` = listo.
280
+ *
281
+ * Vive aquí y no en el anuncio porque el `alta` se emite también en los dos flancos de
282
+ * cada turno, y esos anuncios no están preparando nada: quien lo pone y lo quita es el
283
+ * arranque de la conexión, y los demás leen lo que haya.
284
+ */
285
+ let preparando;
248
286
  const destinoActual = () => vestibulo.proyectoAbierto() ?? vestibulo.consola;
249
287
  const emitir = (mensaje) => {
250
288
  for (const cliente of clientes)
@@ -436,6 +474,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
436
474
  : {}),
437
475
  ...(vestibulo.nombre === undefined ? {} : { nombre: vestibulo.nombre }),
438
476
  ...(aviso === undefined ? {} : { aviso }),
477
+ // Ausente = listo, y solo entonces el cliente sale del lienzo. Se lee de la variable
478
+ // en cada anuncio en vez de pasarse por parámetro: los anuncios de los flancos del
479
+ // turno no preparan nada y tienen que decir lo que hay, no lo que había.
480
+ ...(preparando === undefined ? {} : { preparando }),
439
481
  });
440
482
  };
441
483
  /**
@@ -471,9 +513,15 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
471
513
  motor: a.motor,
472
514
  ...(a.modelo === undefined ? {} : { modelo: a.modelo }),
473
515
  soloLectura: a.soloLectura,
516
+ // Ausente = no la tiene. No se emite `false` para que el cliente no tenga que
517
+ // distinguir entre «no» y «no consta»: aquí son lo mismo y la ausencia lo dice.
518
+ ...(a.ejecucion === true ? { ejecucion: true } : {}),
474
519
  skills: a.skills,
475
520
  instrucciones: a.instrucciones,
476
521
  origen: a.origen,
522
+ // Ausente cuando el `.md` es del usuario, y eso es el dato: es lo que le pone la
523
+ // papelera en vez de un «Restaurar el de serie».
524
+ ...(a.semilla === undefined ? {} : { semilla: a.semilla }),
477
525
  })),
478
526
  problemas,
479
527
  };
@@ -489,6 +537,52 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
489
537
  */
490
538
  const atenderAgente = (mensaje) => {
491
539
  const abierto = vestibulo.proyectoAbierto();
540
+ /**
541
+ * `restaurar` va ANTES de la guarda del ámbito porque no tiene ámbito que elegir: la
542
+ * siembra solo escribe en el global, así que la versión de serie solo puede volver ahí.
543
+ * Pasarlo por el `ambito` del mensaje sería ofrecer una decisión que no existe, y con un
544
+ * `ambito: "proyecto"` daría un «no hay ningún proyecto abierto» que no viene a cuento.
545
+ */
546
+ if (mensaje.accion === "restaurar") {
547
+ informar(restaurarAgente(homedir(), mensaje.agente.nombre)
548
+ ? `subagente «${mensaje.agente.nombre}» restaurado al de serie`
549
+ : `«${mensaje.agente.nombre}» no es uno de los de serie: no hay ninguna versión nuestra que poner`);
550
+ emitirAgentes();
551
+ return;
552
+ }
553
+ /**
554
+ * Y un de serie NO se borra. La negativa vive AQUÍ y no solo en el cliente, que le
555
+ * esconde el icono: esconderlo es presentación, y este mensaje lo puede mandar cualquier
556
+ * cosa que hable por el cable. Borrarlo no devolvía el de serie —`sembrarAgentes` no
557
+ * resiembra lo que consta entregado— así que era perderlo para siempre; lo que sí hace
558
+ * lo que aquello prometía es `restaurar`, y se dice en el mismo sitio donde se rechaza.
559
+ */
560
+ if (mensaje.accion === "borrar" && mensaje.ambito === "global" && esDeSerie(mensaje.agente.nombre)) {
561
+ informar(`«${mensaje.agente.nombre}» lo trae xonecode y no se borra: edítalo, o restaura el de serie si quieres el original`);
562
+ return;
563
+ }
564
+ /**
565
+ * Un renombrado de verdad: `renombrandoDe` puesto y DISTINTO del nombre nuevo. Con el
566
+ * mismo nombre es un guardado normal, y tratarlo de renombrado lo haría fallar por
567
+ * «destino ocupado» contra sí mismo.
568
+ */
569
+ const renombrandoDe = mensaje.accion === "guardar" &&
570
+ mensaje.renombrandoDe !== undefined &&
571
+ mensaje.renombrandoDe !== mensaje.agente.nombre
572
+ ? mensaje.renombrandoDe
573
+ : undefined;
574
+ /**
575
+ * Y un de serie tampoco se RENOMBRA, por lo mismo que no se borra: su nombre es lo que lo
576
+ * ata a la marca de la siembra (`.semilla.json` guarda el hash POR NOMBRE), así que
577
+ * moverlo lo convierte en un subagente del usuario y hace que el de serie vuelva a
578
+ * sembrarse al arrancar — dos especialistas donde había uno. La regla vive aquí y no en
579
+ * `renombrarAgente`, que mueve un fichero: dos sitios donde decidir lo mismo es cómo uno
580
+ * de los dos se queda sin la regla.
581
+ */
582
+ if (renombrandoDe !== undefined && mensaje.ambito === "global" && esDeSerie(renombrandoDe)) {
583
+ informar(`«${renombrandoDe}» lo trae xonecode y su nombre no se cambia: es lo que lo ata a la versión de serie`);
584
+ return;
585
+ }
492
586
  if (mensaje.ambito === "proyecto" && abierto === undefined) {
493
587
  informar("no hay ningún proyecto abierto: ese subagente solo se puede guardar como global");
494
588
  return;
@@ -515,8 +609,35 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
515
609
  informar(`no se guarda «${candidato.nombre}»: ${comprobado.error}`);
516
610
  return;
517
611
  }
518
- guardarAgente(base, candidato);
519
- informar(`subagente «${candidato.nombre}» guardado en ${mensaje.ambito}`);
612
+ /**
613
+ * Y el NOMBRE tiene que ser un slug, aquí y no en el cargador: cargando solo se DICE
614
+ * (`leerCarpetaDeAgentes`), porque rechazar al cargar haría desaparecer un subagente
615
+ * que funciona. Guardar es el único momento con alguien delante para arreglarlo.
616
+ *
617
+ * No lo cubre `leerAgente`: el nombre no sale del `.md`, sale del FICHERO, así que
618
+ * escribir y volver a leer no lo mira. Y tampoco `segmentoSeguro`, que impide salir de
619
+ * la carpeta y nada más — `Documentador` pasa esa y no es un slug.
620
+ */
621
+ const malNombre = motivoDeNombreInaceptable(candidato.nombre);
622
+ if (malNombre !== undefined) {
623
+ informar(`no se guarda «${candidato.nombre}»: el nombre ${malNombre}`);
624
+ return;
625
+ }
626
+ if (renombrandoDe === undefined) {
627
+ guardarAgente(base, candidato);
628
+ informar(`subagente «${candidato.nombre}» guardado en ${mensaje.ambito}`);
629
+ }
630
+ else {
631
+ // Los tres desenlaces se dicen distintos: el destino ocupado y el origen que ya no
632
+ // está son dos situaciones que el usuario arregla de formas distintas, y un «no se
633
+ // pudo» para las dos le haría probar la equivocada.
634
+ const como = renombrarAgente(base, renombrandoDe, candidato);
635
+ informar(como === "hecho"
636
+ ? `subagente «${renombrandoDe}» renombrado a «${candidato.nombre}»`
637
+ : como === "sin-origen"
638
+ ? `«${renombrandoDe}» ya no existía en ${mensaje.ambito}: no se ha renombrado`
639
+ : `ya hay un subagente «${candidato.nombre}» en ${mensaje.ambito}: elige otro nombre, o bórralo primero`);
640
+ }
520
641
  }
521
642
  }
522
643
  catch (error) {
@@ -524,6 +645,132 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
524
645
  }
525
646
  emitirAgentes();
526
647
  };
648
+ /**
649
+ * Las skills en vigor, compuestas pero sin mandar.
650
+ *
651
+ * `cargarSkills` cada vez y no una lista cacheada, por lo mismo que `mensajeDeAgentes`:
652
+ * las carpetas se pueden tocar a mano con la consola abierta, y una lista congelada al
653
+ * arrancar haría creer que el cambio no se aplicó.
654
+ *
655
+ * **El `cuerpo` viaja solo en las del USUARIO.** Las de serie son nueve ficheros que pasan
656
+ * de veinte mil caracteres cada uno; mandarlos en la ráfaga de bienvenida es pagar cientos
657
+ * de kilobytes por rellenar un formulario que nadie puede guardar. El suyo se pide a mano
658
+ * (`cuerpoDeSkill`), que es cuando alguien la está mirando de verdad.
659
+ */
660
+ const mensajeDeSkills = () => {
661
+ const abierto = vestibulo.proyectoAbierto();
662
+ const { skills, problemas } = cargarSkills(abierto?.raiz);
663
+ return {
664
+ clase: "skills",
665
+ skills: skills.map((s) => ({
666
+ nombre: s.nombre,
667
+ descripcion: s.descripcion,
668
+ origen: s.origen,
669
+ tokens: s.tokens,
670
+ ficheros: s.ficheros,
671
+ // El frontmatter va SIEMPRE: son cuatro líneas, y es lo que contesta qué declara el
672
+ // fichero. El CUERPO no, en las de serie: eso sí son decenas de miles de caracteres.
673
+ ...(s.frontmatter === undefined ? {} : { frontmatter: s.frontmatter }),
674
+ ...(s.origen === "serie" ? {} : { cuerpo: s.cuerpo }),
675
+ })),
676
+ problemas,
677
+ };
678
+ };
679
+ const emitirSkills = () => emitir(mensajeDeSkills());
680
+ /**
681
+ * El cuerpo de UNA skill, bajo demanda: al abrir la ficha de una de serie o al copiarla.
682
+ *
683
+ * Se contesta SOLO a quien lo pidió no se puede —el transporte habla con todos—, así que
684
+ * va a todos y el cliente se queda con la que tiene abierta. Es un fichero de texto que ya
685
+ * está en la lista, no un secreto.
686
+ *
687
+ * Lo que no se pudo leer se DICE con un `error`: una ficha que se abre en blanco se lee
688
+ * como que la skill está vacía, y entonces copiarla produciría una copia vacía.
689
+ */
690
+ const atenderCuerpoDeSkill = (mensaje) => {
691
+ const abierto = vestibulo.proyectoAbierto();
692
+ const skill = cargarSkills(abierto?.raiz).skills.find((s) => s.nombre === mensaje.nombre);
693
+ emitir(skill === undefined
694
+ ? { clase: "cuerpoDeSkill", nombre: mensaje.nombre, error: "ya no está en el catálogo" }
695
+ : { clase: "cuerpoDeSkill", nombre: mensaje.nombre, cuerpo: skill.cuerpo });
696
+ };
697
+ /**
698
+ * Alta, cambio y borrado de una skill del usuario. El mismo molde que `atenderAgente`.
699
+ *
700
+ * **Las de serie no se tocan, y la barrera está AQUÍ.** Viven dentro del paquete
701
+ * instalado: una edición se la llevaría el siguiente `npm install` sin decir nada, y un
702
+ * borrado no se puede reponer porque nada nuestro se copia nunca a la casa del usuario
703
+ * —no hay marca de siembra que consultar, a diferencia de los subagentes—. Lo que la
704
+ * consola ofrece sobre una de serie es COPIARLA, que es un `guardar` con otro nombre.
705
+ *
706
+ * `guardarSkill` DEVUELVE el motivo en vez de lanzar, así que los tres desenlaces —el
707
+ * nombre ocupado, el nombre de una de serie y el fallo de escritura— se dicen distintos:
708
+ * son tres cosas que el usuario arregla de tres formas.
709
+ */
710
+ const atenderSkill = (mensaje) => {
711
+ const abierto = vestibulo.proyectoAbierto();
712
+ const nombre = mensaje.skill.nombre;
713
+ if (mensaje.ambito === "proyecto" && abierto === undefined) {
714
+ informar("no hay ningún proyecto abierto: esa skill solo se puede guardar como global");
715
+ return;
716
+ }
717
+ const base = mensaje.ambito === "proyecto" ? abierto.raiz : homedir();
718
+ /**
719
+ * Borrar se INTENTA y el motivo sale de lo que pasó, no de una guarda previa por NOMBRE.
720
+ *
721
+ * La guarda por nombre era demasiado ancha: una skill del usuario que se llame como una
722
+ * de serie —tapándola, que es la forma de afinarla sin editar donde el `npm install` la
723
+ * pisaría— no se podía borrar nunca, porque `esDeSerie` mira el catálogo del paquete y no
724
+ * la carpeta del usuario. Lo que hay en la carpeta del usuario es del usuario y se borra;
725
+ * lo que no está ahí y ADEMÁS lo traemos nosotros es lo que se niega, con el camino al
726
+ * lado. Un solo sitio decide, que es `borrarSkill` contestando si había algo.
727
+ */
728
+ if (mensaje.accion === "borrar") {
729
+ informar(borrarSkill(base, nombre)
730
+ ? `skill «${nombre}» borrada`
731
+ : esDeSerieLaSkill(nombre)
732
+ ? `la skill «${nombre}» la trae xonecode y no se borra: cópiala si quieres partir de ella`
733
+ : `«${nombre}» no existía en ${mensaje.ambito}`);
734
+ emitirSkills();
735
+ return;
736
+ }
737
+ /**
738
+ * El nombre tiene que ser un slug, y se comprueba al GUARDAR y no al cargar: rechazar al
739
+ * cargar haría desaparecer una skill que funciona, y guardar es el único momento con
740
+ * alguien delante para arreglarlo. `segmentoSeguro` —que `guardarSkill` sí aplica— solo
741
+ * impide salir de la carpeta: `Mi Skill` la pasa y no es un slug, y el nombre de una
742
+ * skill viaja además dentro del `.md` de un subagente.
743
+ */
744
+ const malNombre = motivoDeNombreDeSkillInaceptable(nombre);
745
+ if (malNombre !== undefined) {
746
+ const sugerido = nombreDeSkillSugerido(nombre);
747
+ informar(`no se guarda «${nombre}»: el nombre ${malNombre}` +
748
+ (sugerido === undefined ? "" : ` (prueba «${sugerido}»)`));
749
+ return;
750
+ }
751
+ if (mensaje.skill.descripcion.trim() === "") {
752
+ // Es lo que el modelo lee para decidir si le sirve: sin ella la skill está en el
753
+ // catálogo y no la usa nadie nunca, que es peor que no estar.
754
+ informar(`no se guarda «${nombre}»: sin descripción, el orquestador no sabría cuándo usarla`);
755
+ return;
756
+ }
757
+ const renombrandoDe = mensaje.renombrandoDe !== undefined && mensaje.renombrandoDe !== nombre
758
+ ? mensaje.renombrandoDe
759
+ : undefined;
760
+ const escrita = guardarSkill(base, {
761
+ nombre,
762
+ descripcion: mensaje.skill.descripcion,
763
+ cuerpo: mensaje.skill.cuerpo ?? "",
764
+ // El frontmatter de ANTES, para conservar lo que no editamos. Ausente en un alta.
765
+ ...(mensaje.skill.frontmatter === undefined ? {} : { frontmatter: mensaje.skill.frontmatter }),
766
+ }, mensaje.renombrandoDe);
767
+ informar("error" in escrita
768
+ ? `no se guarda «${nombre}»: ${escrita.error}`
769
+ : renombrandoDe === undefined
770
+ ? `skill «${nombre}» guardada en ${mensaje.ambito}`
771
+ : `skill «${renombrandoDe}» renombrada a «${nombre}»`);
772
+ emitirSkills();
773
+ };
527
774
  /** El mensaje de modelos, compuesto pero sin mandar: `adjuntar` se lo da SOLO al cliente
528
775
  * que acaba de llegar, y el resto de sitios lo emite a todos. */
529
776
  const mensajeDeModelos = () => {
@@ -602,6 +849,10 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
602
849
  // se puede abrir en cuanto conecta, y sin esto enseñaría una lista vacía hasta que algo
603
850
  // los cambiara — que es indistinguible de «no tienes ninguno».
604
851
  const agentes = mensajeDeAgentes();
852
+ // Y las skills, por lo mismo que los subagentes y con una razón más: el editor de un
853
+ // subagente pinta sus skills como una lista de casillas, así que sin esto ese formulario
854
+ // se abriría sin ninguna casilla que marcar — que es indistinguible de «no hay ninguna».
855
+ const skills = mensajeDeSkills();
605
856
  // La cola de tareas, si esta ejecución las tiene: la misma regla que `agentes`, sin
606
857
  // esto la pestaña de tareas se quedaría vacía hasta el primer cambio de la cola.
607
858
  const tareas = mensajeDeTareas();
@@ -619,6 +870,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
619
870
  cliente({ clase: "reemision", actos: [...actos] });
620
871
  cliente(modelos);
621
872
  cliente(agentes);
873
+ cliente(skills);
622
874
  if (tareas !== undefined)
623
875
  cliente(tareas);
624
876
  if (consumoDeLaSesion !== undefined) {
@@ -708,20 +960,33 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
708
960
  * heredar un «ya se intentó» que se quedó en `proyectos: []` para siempre. Esto puede
709
961
  * abrir el navegador de verdad si el token necesita reautenticar —`conectarCloudStudio`
710
962
  * 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
963
+ * absoluto (arreglado en `agent/cloudstudio/cloudstudioMcp.ts#abrirCliente`), así que dos conexiones
712
964
  * seguidas no chocan por intentarlo cada una.
713
965
  */
714
- const poblarProyectosSiProcede = async () => {
966
+ /**
967
+ * De qué entorno se poblarían los proyectos al arrancar, o nada si no hay nada que
968
+ * preparar.
969
+ *
970
+ * Es la MISMA decisión que toma `poblarProyectosSiProcede`, extraída para que el arranque
971
+ * pueda anunciar la fase ANTES de empezarla. En dos sitios divergirían, y el precio sería
972
+ * de los peores: el lienzo esperando por un trabajo que nadie va a pedir (`preparando`
973
+ * puesto y nada que lo quite), o entrar en vacío justo cuando sí había algo que traer.
974
+ */
975
+ const entornoAPoblar = () => {
715
976
  if (vestibulo.proyectoAbierto() !== undefined)
716
- return;
977
+ return undefined;
717
978
  if (entornoElegido !== undefined)
718
- return;
979
+ return undefined;
719
980
  const [primero] = vestibulo.entornosRegistrados();
720
- if (primero === undefined)
981
+ return primero === undefined ? undefined : { id: primero.id, nombre: primero.nombre };
982
+ };
983
+ const poblarProyectosSiProcede = async () => {
984
+ const destino = entornoAPoblar();
985
+ if (destino === undefined)
721
986
  return;
722
987
  try {
723
- proyectos = await vestibulo.proyectosDe(primero.id);
724
- entornoElegido = primero.id;
988
+ proyectos = await vestibulo.proyectosDe(destino.id);
989
+ entornoElegido = destino.id;
725
990
  }
726
991
  catch (error) {
727
992
  aviso = error instanceof Error ? error.message : String(error);
@@ -1033,7 +1298,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1033
1298
  /**
1034
1299
  * «Se edita la tarea y se agrega el feedback del usuario» (§0 del diseño, textual):
1035
1300
  * 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
1301
+ * `aplicarFeedback` (`agent/tareas/tareasEnDisco.ts`) y no repite su lógica aquí, por el mismo
1037
1302
  * motivo que `atenderCrearTarea`/`atenderAccionDeTarea` no reimplementan `conEstado`: la
1038
1303
  * regla de qué feedback vale y qué transición es legal está en una sola función, probada
1039
1304
  * sola y sin necesitar un servidor de mentira alrededor.
@@ -1051,6 +1316,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1051
1316
  /** Los ajustes de destino, o `{}` —que es «se miran todos»— si esta ejecución no los lee. */
1052
1317
  const ajustesDeDispositivos = () => opciones.ajustesDeDispositivos?.() ?? {};
1053
1318
  let deteccionEnVuelo;
1319
+ /**
1320
+ * Cómo acabó el último «Arrancar». Viaja con la foto porque el resultado de arrancar ES la
1321
+ * foto: `emulator` devuelve el control enseguida y lo que importa es si el aparato apareció.
1322
+ * Se limpia al pedir una medida sin arranque de por medio, para no repetir un acuse viejo.
1323
+ */
1324
+ let ultimoArranque;
1054
1325
  const atenderDispositivos = () => {
1055
1326
  if (opciones.detectarDispositivos === undefined)
1056
1327
  return Promise.resolve();
@@ -1060,7 +1331,12 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1060
1331
  deteccionEnVuelo = (async () => {
1061
1332
  try {
1062
1333
  informeDeDispositivos = sinRutas(await detectar());
1063
- emitir({ clase: "dispositivos", informe: informeDeDispositivos, ajustes: ajustesDeDispositivos() });
1334
+ emitir({
1335
+ clase: "dispositivos",
1336
+ informe: informeDeDispositivos,
1337
+ ajustes: ajustesDeDispositivos(),
1338
+ ...(ultimoArranque === undefined ? {} : { arranque: ultimoArranque }),
1339
+ });
1064
1340
  }
1065
1341
  catch (error) {
1066
1342
  // El detector reparte los fallos por herramienta y no lanza por ninguno; que
@@ -1075,6 +1351,55 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1075
1351
  })();
1076
1352
  return deteccionEnVuelo;
1077
1353
  };
1354
+ /**
1355
+ * **Arrancar un AVD, y contarlo con la foto de después.**
1356
+ *
1357
+ * Tres guardas, en este orden:
1358
+ *
1359
+ * 1. **Que esta ejecución pueda.** Sin el puerto no hay arranque y se DICE: un botón que
1360
+ * llega a un servidor que no sabe arrancar nada es el control muerto de siempre.
1361
+ * 2. **Que ese AVD esté en NUESTRA última medida.** El nombre llega por el cable y acaba
1362
+ * siendo un argumento de proceso; la lista de AVDs es lo único que lo autoriza. Sin
1363
+ * medida todavía no se arranca nada — no hay con qué comprobarlo.
1364
+ * 3. **Uno a la vez.** Dos arranques del mismo AVD a la vez son dos emuladores peleándose
1365
+ * por el mismo puerto, y de otro AVD sería trabajo pesado en paralelo en la máquina de
1366
+ * quien mira. Se reenvía el estado, que es lo que hace la receta.
1367
+ *
1368
+ * Al acabar se vuelve a medir SIEMPRE, salga bien o mal: la foto es la que dice si arrancó.
1369
+ */
1370
+ let arranqueEnVuelo;
1371
+ const atenderArranqueDeEmulador = async (avd) => {
1372
+ const arrancar = opciones.arrancarEmulador;
1373
+ if (arrancar === undefined) {
1374
+ informar("esta ejecución no puede arrancar emuladores");
1375
+ return;
1376
+ }
1377
+ if (arranqueEnVuelo !== undefined) {
1378
+ informar(`ya estoy arrancando ${arranqueEnVuelo}`);
1379
+ return;
1380
+ }
1381
+ if (informeDeDispositivos === undefined || !informeDeDispositivos.avds.includes(avd)) {
1382
+ ultimoArranque = { avd, ok: false, detalle: `no consta ningún AVD llamado «${avd}» en la última medida` };
1383
+ await atenderDispositivos().catch(contar);
1384
+ return;
1385
+ }
1386
+ arranqueEnVuelo = avd;
1387
+ try {
1388
+ const r = await arrancar(avd);
1389
+ ultimoArranque = { avd, ...r };
1390
+ }
1391
+ catch (error) {
1392
+ // El puerto promete no lanzar; si lanzara, el botón no puede quedarse mudo.
1393
+ ultimoArranque = { avd, ok: false, detalle: error instanceof Error ? error.message : String(error) };
1394
+ contar(error);
1395
+ }
1396
+ finally {
1397
+ arranqueEnVuelo = undefined;
1398
+ // La medida nueva, pase lo que pase: es la que dice la verdad.
1399
+ informeDeDispositivos = undefined;
1400
+ await atenderDispositivos().catch(contar);
1401
+ }
1402
+ };
1078
1403
  /**
1079
1404
  * Verificar la conexión con UN dispositivo, y volver a emitir la foto con lo que contestó.
1080
1405
  *
@@ -1129,7 +1454,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1129
1454
  }
1130
1455
  catch (error) {
1131
1456
  // `ErrorCatalogoModelos` es publicable por contrato: nunca lleva la clave ni el
1132
- // cuerpo remoto (`agent/catalogoModelos.ts`).
1457
+ // cuerpo remoto (`agent/config/catalogoModelos.ts`).
1133
1458
  catalogos.set(id, { error: error instanceof Error ? error.message : String(error) });
1134
1459
  }
1135
1460
  emitirModelos();
@@ -1470,6 +1795,31 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1470
1795
  // El cable se muda a la consola del proyecto, como en el alta: sin esto el usuario
1471
1796
  // mira un transcript vivo cuyas aprobaciones se rechazan solas al otro lado.
1472
1797
  adjuntar();
1798
+ /**
1799
+ * Y el contador se REEMITE, porque el gasto es de UNA conversación.
1800
+ *
1801
+ * `consumo` solo viaja cuando el consumo CAMBIA (`alCambiarConsumo`), y abrir otra
1802
+ * sesión no cambia nada: cambia de quién es la cuenta. Sin esto —medido en la pantalla
1803
+ * del usuario— la caja seguía enseñando los tokens de la conversación anterior hasta la
1804
+ * primera llamada al modelo de la nueva, que puede tardar.
1805
+ *
1806
+ * **No se usa `emitirConsumo()`, y ese es el detalle que costó el primer intento**: esa
1807
+ * función CALLA cuando no consta la cuenta, y al abrir no consta — el `SesionReal` no se
1808
+ * registra hasta que el primer turno lo construye. O sea que justo en el instante que
1809
+ * hay que limpiar, la vía normal no manda nada. Aquí el cero SÍ está medido: una
1810
+ * conversación recién abierta no ha gastado nada, y su tracker nace a cero aunque la
1811
+ * sesión sea vieja. El cliente no pinta un cero, así que el contador desaparece.
1812
+ *
1813
+ * Y si la cuenta SÍ consta —volver al foco de una consola que está corriendo un turno—
1814
+ * se manda la de verdad: mandar cero ahí borraría un contador vivo a mitad de turno.
1815
+ */
1816
+ const alAbrir = vestibulo.consumoDeSesion();
1817
+ emitir({
1818
+ clase: "consumo",
1819
+ modelo: alAbrir?.modelo ?? SIN_CONSUMO,
1820
+ externo: alAbrir?.externo ?? SIN_CONSUMO,
1821
+ ventana: ventanaDeAhora(alAbrir?.contexto ?? 0),
1822
+ });
1473
1823
  // Y la cola se vuelve a mirar: este proyecto queda bloqueado para las tareas —gana la
1474
1824
  // persona— y el que estuviera abierto antes acaba de quedar libre.
1475
1825
  opciones.revisarTareas?.();
@@ -1761,7 +2111,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1761
2111
  };
1762
2112
  /**
1763
2113
  * La sincronización con CloudStudio del proyecto abierto, pedida desde un control
1764
- * (la banda de arriba de Revisión, y «Volver a mirar»).
2114
+ * (la banda de arriba de Revisión, y «Refrescar»).
1765
2115
  *
1766
2116
  * **`estado` se mide aquí; `subir` y `bajar` se ENCOLAN**, y la asimetría es deliberada.
1767
2117
  * Medir es leer una ref de git que ya está en local (`lecturaDeSync`), así que encolarla
@@ -1778,7 +2128,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
1778
2128
  * Y lo que NO se hace es volver a emitir la lectura después de encolar: el servidor no
1779
2129
  * sabe cuándo termina una línea de la cola, y un «3 ficheros por subir» recién emitido
1780
2130
  * 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 «Volver a mirar», que es cuando de verdad se mira.
2131
+ * al volver a ella y al pulsar «Refrescar», que es cuando de verdad se mira.
1782
2132
  */
1783
2133
  const atenderSync = (accion) => {
1784
2134
  const abierto = vestibulo.proyectoAbierto();
@@ -2512,10 +2862,48 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2512
2862
  // documenta la medida).
2513
2863
  // Solo al recién llegado: los demás ya recibieron su saludo al conectar.
2514
2864
  sumidero({ clase: "bienvenida", ...(vestibulo.nombre === undefined ? {} : { nombre: vestibulo.nombre }) });
2865
+ /**
2866
+ * **Dos anuncios, y el primero va ANTES de preguntar a CloudStudio.**
2867
+ *
2868
+ * `pasos` se calcula del DISCO (`pasosPendientes`: de dónde vino el modelo y qué entornos
2869
+ * hay registrados), así que en una máquina configurada ya se sabe «no falta nada» sin
2870
+ * tocar la red. Con un solo anuncio detrás de `poblarProyectosSiProcede` —que abre la
2871
+ * sesión MCP— el cliente no podía saberlo hasta que ese viaje terminara, y `App.tsx` lee
2872
+ * un `alta` ausente como «todavía no consta»: enseñaba la pantalla del alta a alguien que
2873
+ * no tiene nada que dar de alta. Medido en la máquina del usuario, con CloudStudio
2874
+ * caliente: el `alta` llegaba a 1436 ms, y con el MCP lento o la red caída esa ventana
2875
+ * crece hasta su tope. Es la misma forma que `MS_DE_TRABAJO_AL_ABRIR`: un dato que se
2876
+ * sabe y no se dice porque va detrás de otro que tarda.
2877
+ *
2878
+ * El segundo anuncio lleva los proyectos, y sigue en un `finally` por lo de siempre: que
2879
+ * `poblarProyectosSiProcede` acabe mal no puede dejar al cliente sin el anuncio. Es el
2880
+ * mismo reparto que ya usaba el cambio de entorno (anunciar, luego preguntar).
2881
+ */
2882
+ /**
2883
+ * La fase se pone ANTES del primer anuncio y se quita en el `finally`, así que el primer
2884
+ * `alta` sale con ella y el segundo sin ella. Es lo que hace que el Escritorio no entre
2885
+ * hasta estar preparado —lo que el usuario pidió— sin volver a esconder el «no falta
2886
+ * ningún paso», que es lo que el `alta` adelantado vino a arreglar: el cliente sabe
2887
+ * desde el primer mensaje que no hay nada que dar de alta Y que todavía está trayendo
2888
+ * los proyectos, que son dos cosas distintas.
2889
+ *
2890
+ * Con la cuenta pendiente y un entorno ya registrado las dos cosas pasan a la vez, y eso
2891
+ * está bien: la tarjeta pinta el paso de cuenta mientras el MCP trabaja por detrás. Es el
2892
+ * cliente quien decide qué se ve, porque la fase es un dato, no una orden de pintar.
2893
+ */
2894
+ const aPreparar = entornoAPoblar();
2895
+ preparando = aPreparar === undefined ? undefined : `conectando con ${aPreparar.nombre}…`;
2515
2896
  void conducirCuenta()
2516
2897
  .catch(contar)
2517
- .then(() => poblarProyectosSiProcede())
2518
- .finally(() => void anunciarAlta().catch(contar));
2898
+ .then(() => anunciarAlta().catch(contar))
2899
+ .then(() => conPlazo(poblarProyectosSiProcede(), MS_DE_PREPARACION))
2900
+ .finally(() => {
2901
+ // Se quita SIEMPRE, también cuando el plazo vence con el MCP todavía colgado: el
2902
+ // lienzo no puede quedarse esperando a quien no contesta. Lo que llegue tarde entra
2903
+ // en el siguiente anuncio, y lo que falló ya viaja en `aviso`.
2904
+ preparando = undefined;
2905
+ void anunciarAlta().catch(contar);
2906
+ });
2519
2907
  peticion.on("close", () => {
2520
2908
  // Se va ESTE cliente, no «el cliente». La guarda de antes (`enviar !== sumidero`)
2521
2909
  // existía porque el `close` de una pestaña recargada puede llegar DESPUÉS de que el
@@ -2675,6 +3063,61 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2675
3063
  * - **Ninguna respuesta lleva una ruta de la máquina** ni nada de lo recibido, que es la
2676
3064
  * regla de `POST /accion`.
2677
3065
  */
3066
+ /**
3067
+ * `POST /skill?nombre=<fichero.zip>&ambito=<global|proyecto>` — instalar una skill.
3068
+ *
3069
+ * Por HTTP y no por el cable, con el mismo molde que `POST /adjunto`: esto son bytes y el
3070
+ * cable lleva JSON. `Host`, `Origin` y token los comprueba `servidor.ts` antes de llegar
3071
+ * aquí, igual que a todas las rutas. Lo propio de ésta:
3072
+ *
3073
+ * - **El `nombre` de la query es el del FICHERO que subió la persona, y solo se usa como
3074
+ * nombre de respaldo** cuando el zip no trae una carpeta de la que sacarlo
3075
+ * (`core/zipDeSkill.ts`). No se compone ninguna ruta con él sin pasar por la regla del
3076
+ * slug, que es la misma de un subagente.
3077
+ * - **El cuerpo se lee con el tope del ZIP y se corta ahí** (413), sin acumular. Y el otro
3078
+ * tope, el de lo DESCOMPRIMIDO, lo pone `planDeInstalacion`: éste no lo ve, que es
3079
+ * justamente lo que un zip bomba aprovecha.
3080
+ * - **Ninguna respuesta lleva una ruta de la máquina ni nada de lo recibido**, que es la
3081
+ * regla de `POST /accion` — y aquí lo recibido es un fichero de alguien.
3082
+ */
3083
+ servidor.registrarRuta("POST", RUTA_SKILL, async (peticion, respuesta) => {
3084
+ const responder = (codigo, texto) => {
3085
+ respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
3086
+ respuesta.end(texto);
3087
+ };
3088
+ const query = new URLSearchParams((peticion.url ?? "").split("?")[1] ?? "");
3089
+ const nombre = query.get("nombre");
3090
+ const ambito = query.get("ambito") === "proyecto" ? "proyecto" : "global";
3091
+ if (nombre === null || nombre === "") {
3092
+ responder(400, "falta «nombre»");
3093
+ return;
3094
+ }
3095
+ const abierto = vestibulo.proyectoAbierto();
3096
+ if (ambito === "proyecto" && abierto === undefined) {
3097
+ responder(409, "no hay ningún proyecto abierto: esa skill solo se puede instalar como global");
3098
+ return;
3099
+ }
3100
+ let datos;
3101
+ try {
3102
+ datos = await leerCuerpoCrudo(peticion, TOPE_DEL_ZIP);
3103
+ }
3104
+ catch {
3105
+ responder(413, "el .zip es demasiado grande");
3106
+ return;
3107
+ }
3108
+ const instalada = instalarSkillDesdeZip(ambito === "proyecto" ? abierto.raiz : homedir(), new Uint8Array(datos), nombre);
3109
+ if ("error" in instalada) {
3110
+ // El motivo lo escribe el módulo y no lleva ninguna ruta de la máquina ni el nombre de
3111
+ // una entrada del zip (sus tests lo vigilan). 422: el zip llegó entero y no vale.
3112
+ informar(`no se instala la skill: ${instalada.error}`);
3113
+ responder(422, instalada.error);
3114
+ return;
3115
+ }
3116
+ informar(`skill «${instalada.nombre}» instalada en ${ambito}`);
3117
+ emitirSkills();
3118
+ respuesta.writeHead(204);
3119
+ respuesta.end();
3120
+ });
2678
3121
  servidor.registrarRuta("POST", RUTA_ADJUNTO, async (peticion, respuesta) => {
2679
3122
  const responder = (codigo, texto) => {
2680
3123
  respuesta.writeHead(codigo, { "Content-Type": "text/plain; charset=utf-8" });
@@ -2808,6 +3251,17 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2808
3251
  respuesta.end();
2809
3252
  return;
2810
3253
  }
3254
+ if (typeof mensaje === "object" &&
3255
+ mensaje !== null &&
3256
+ mensaje.clase === "arrancarEmulador" &&
3257
+ typeof mensaje.avd === "string") {
3258
+ // Se contesta ya: arrancar tarda lo que tarda un emulador y la respuesta del POST no es
3259
+ // el sitio donde se cuenta — eso viaja por el SSE con la foto nueva.
3260
+ void atenderArranqueDeEmulador(mensaje.avd).catch(contar);
3261
+ respuesta.writeHead(204);
3262
+ respuesta.end();
3263
+ return;
3264
+ }
2811
3265
  /**
2812
3266
  * ANTES del `recibir` de la consola, y también antes que las demás clases de tarea: por
2813
3267
  * aquí no entra nada hacia ningún turno. Ver `atenderMirar`.
@@ -2918,6 +3372,18 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
2918
3372
  respuesta.end();
2919
3373
  return;
2920
3374
  }
3375
+ if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "skill") {
3376
+ atenderSkill(mensaje);
3377
+ respuesta.writeHead(204);
3378
+ respuesta.end();
3379
+ return;
3380
+ }
3381
+ if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "cuerpoDeSkill") {
3382
+ atenderCuerpoDeSkill(mensaje);
3383
+ respuesta.writeHead(204);
3384
+ respuesta.end();
3385
+ return;
3386
+ }
2921
3387
  if (typeof mensaje === "object" && mensaje !== null && mensaje.clase === "proveedor") {
2922
3388
  atenderProveedor(mensaje);
2923
3389
  respuesta.writeHead(204);
@@ -3034,6 +3500,19 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3034
3500
  respuesta.end();
3035
3501
  return;
3036
3502
  }
3503
+ /**
3504
+ * **Y el acuse del último arranque se OLVIDA.**
3505
+ *
3506
+ * Sin esto se queda pegado para siempre: el usuario vio `pixel8 · apagado` con un
3507
+ * «✓ pixel8 arrancado» al lado, o sea las dos cosas a la vez. El acuse pertenece a la
3508
+ * foto que se tomó justo después de arrancar, no a todas las de después — y ahora que
3509
+ * la sección remide al entrar y al volver, «todas las de después» son muchas.
3510
+ *
3511
+ * Ausente vuelve a significar «no se ha pedido ningún arranque en esta medida», que es
3512
+ * lo que el campo declara. El `finally` de `atenderArranqueDeEmulador` lo pone y se
3513
+ * salta este camino, así que su propia medida sí lo lleva.
3514
+ */
3515
+ ultimoArranque = undefined;
3037
3516
  informeDeDispositivos = undefined;
3038
3517
  void atenderDispositivos().catch(contar);
3039
3518
  respuesta.writeHead(204);
@@ -3080,7 +3559,7 @@ export function montarRutas(servidor, vestibulo, opciones = {}) {
3080
3559
  * Construye el corredor de tareas y la costura que lo conecta al cable.
3081
3560
  *
3082
3561
  * 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
3562
+ * `backendDeAgente` (`agent/grafo/proyecto.ts`): la composición vivía inline dentro de una
3084
3563
  * función que todos sus tests DOBLAN —`vestibulo`, `crearServidor`, `crearEjecutor`—, así
3085
3564
  * que esta costura concreta podía dejar de estar montada con el resto en verde. Aquí
3086
3565
  * tiene su propio test que la compone de VERDAD: construye un corredor real
@@ -3673,7 +4152,7 @@ export async function arrancarConsolaWeb(opciones) {
3673
4152
  borrarCredencial,
3674
4153
  guardarCredencial,
3675
4154
  /**
3676
- * El defecto se GUARDA en el `config.json` global (`agent/configEnDisco.ts#guardarModeloGlobal`),
4155
+ * El defecto se GUARDA en el `config.json` global (`agent/config/configEnDisco.ts#guardarModeloGlobal`),
3677
4156
  * que es el último escalón de la precedencia y el único que sobrevive al proceso. Se
3678
4157
  * escribe para los TRES papeles porque elegir un modelo en la interfaz es una frase
3679
4158
  * sobre el producto y no sobre un papel — el mismo criterio que `/modelo`, que fija los
@@ -3699,7 +4178,7 @@ export async function arrancarConsolaWeb(opciones) {
3699
4178
  cambiosDeSesion,
3700
4179
  parcheDeSesion,
3701
4180
  // El proyecto tal como lo ve el agente, para la pestaña Ficheros: mismo filtro, misma
3702
- // barrera de rutas (`agent/arbolDeProyecto.ts`).
4181
+ // barrera de rutas (`agent/grafo/arbolDeProyecto.ts`).
3703
4182
  arbolDelProyecto: async (raiz) => arbolDeProyecto(raiz),
3704
4183
  leerFichero: leerFicheroDeProyecto,
3705
4184
  leerArtefacto: leerArtefactoDeSesion,
@@ -3714,6 +4193,11 @@ export async function arrancarConsolaWeb(opciones) {
3714
4193
  guardarAjustesDeDispositivos: (ajustes) => void guardarDispositivos(undefined, ajustes),
3715
4194
  instalarHerramienta: instalarHerramientaDeDispositivos,
3716
4195
  verificarDispositivo,
4196
+ // Con la función REAL, y no declarada y sin pasar: es la trampa que este repo ha pagado
4197
+ // nueve veces, y la que el comentario de justo abajo describe para las cuatro del
4198
+ // lanzamiento. `montarRutas` lo trata como ausente si falta, así que olvidarlo aquí
4199
+ // dejaría el botón diciendo «esta ejecución no puede arrancar emuladores» con todo verde.
4200
+ arrancarEmulador: (avd) => arrancarEmulador(avd),
3717
4201
  /**
3718
4202
  * Las cuatro de «¿se puede lanzar y lánzalo?», compuestas con las funciones REALES.
3719
4203
  *
@@ -3750,7 +4234,7 @@ export async function arrancarConsolaWeb(opciones) {
3750
4234
  guardarProveedor: (declarado) => guardarProveedorPersonalizado(declarado),
3751
4235
  borrarProveedor: (slug) => borrarProveedorPersonalizado(slug),
3752
4236
  /**
3753
- * El AUMENTADOR de la ventana de crear una tarea (`agent/aumentador.ts`), con el papel
4237
+ * El AUMENTADOR de la ventana de crear una tarea (`agent/tareas/aumentador.ts`), con el papel
3754
4238
  * `trabajo` — es una tarea de redacción, no una clasificación.
3755
4239
  *
3756
4240
  * Tres cosas que se deciden aquí:
@@ -3885,7 +4369,7 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
3885
4369
  // Se resuelve UNA vez, aquí, y no en cada `anunciarAlta`: `git config`/`os.userInfo`
3886
4370
  // no cambian a media conexión, y repetir el subproceso en cada anuncio del alta sería
3887
4371
  // gastar sin motivo. Nunca viaja hacia CloudStudio ni hacia ningún acto —
3888
- // `agent/persona.ts` lo documenta—.
4372
+ // `agent/config/persona.ts` lo documenta—.
3889
4373
  nombre: nombreDePersona(opciones.cwd),
3890
4374
  catalogoModelos: new CatalogoModelos(),
3891
4375
  guardarCredencial,
@@ -3898,7 +4382,7 @@ function vestibuloReal(opciones, servidor, escribir, informar) {
3898
4382
  guardarModeloGlobal,
3899
4383
  guardarConfigDeProyecto: escribirProyectoEnDisco,
3900
4384
  // 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.
4385
+ // sesión tiene id. Ver `agent/sesiones/sesionGit.ts` para por qué es una ref y no un tag.
3902
4386
  marcarSesion: fotoDeApertura,
3903
4387
  // Lo que ya había sin commitear al abrir, para poder decirlo. Comparte el hueco
3904
4388
  // declarado de `marcarSesion`: esta composición vive en un cierre que `vestibuloReal`