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.
Files changed (315) hide show
  1. package/apps/web/dist/assets/{index-BB3SamhR.css → index-Boak51UP.css} +1 -1
  2. package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
  3. package/apps/web/dist/assets/{php-D_2--9WK.js → php-Cu6F_QCJ.js} +1 -1
  4. package/apps/web/dist/assets/{ruby-DSHSDbk1.js → ruby-BiqQPoC6.js} +1 -1
  5. package/apps/web/dist/index.html +2 -2
  6. package/dist/agent/{cloudstudioClient.js → cloudstudio/cloudstudioClient.js} +10 -0
  7. package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -0
  8. package/dist/agent/{cloudstudioMcp.js → cloudstudio/cloudstudioMcp.js} +5 -5
  9. package/dist/agent/cloudstudio/cloudstudioMcp.js.map +1 -0
  10. package/dist/agent/{descarga.js → cloudstudio/descarga.js} +17 -7
  11. package/dist/agent/cloudstudio/descarga.js.map +1 -0
  12. package/dist/agent/cloudstudio/manifiesto.js.map +1 -0
  13. package/dist/agent/cloudstudio/ramaActiva.js +11 -0
  14. package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
  15. package/dist/agent/{subida.js → cloudstudio/subida.js} +16 -7
  16. package/dist/agent/cloudstudio/subida.js.map +1 -0
  17. package/dist/agent/{zip.js → cloudstudio/zip.js} +1 -1
  18. package/dist/agent/cloudstudio/zip.js.map +1 -0
  19. package/dist/agent/{authEnDisco.js → config/authEnDisco.js} +3 -3
  20. package/dist/agent/config/authEnDisco.js.map +1 -0
  21. package/dist/agent/{catalogoModelos.js → config/catalogoModelos.js} +47 -1
  22. package/dist/agent/config/catalogoModelos.js.map +1 -0
  23. package/dist/agent/{configEnDisco.js → config/configEnDisco.js} +6 -6
  24. package/dist/agent/config/configEnDisco.js.map +1 -0
  25. package/dist/agent/{crearProyecto.js → config/crearProyecto.js} +2 -2
  26. package/dist/agent/config/crearProyecto.js.map +1 -0
  27. package/dist/agent/config/entorno.js.map +1 -0
  28. package/dist/agent/config/gemini.js.map +1 -0
  29. package/dist/agent/config/modelos.js +235 -0
  30. package/dist/agent/config/modelos.js.map +1 -0
  31. package/dist/agent/{modelosDeMotor.js → config/modelosDeMotor.js} +1 -1
  32. package/dist/agent/config/modelosDeMotor.js.map +1 -0
  33. package/dist/agent/config/mudanzaEnDisco.js +121 -0
  34. package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
  35. package/dist/agent/{persona.js → config/persona.js} +1 -1
  36. package/dist/agent/config/persona.js.map +1 -0
  37. package/dist/agent/config/selectorEnMaquina.js +45 -0
  38. package/dist/agent/config/selectorEnMaquina.js.map +1 -0
  39. package/dist/agent/{settingsEnDisco.js → config/settingsEnDisco.js} +37 -2
  40. package/dist/agent/config/settingsEnDisco.js.map +1 -0
  41. package/dist/agent/{versionEnDisco.js → config/versionEnDisco.js} +8 -4
  42. package/dist/agent/config/versionEnDisco.js.map +1 -0
  43. package/dist/agent/dispositivos/arranqueDeEmulador.js +146 -0
  44. package/dist/agent/dispositivos/arranqueDeEmulador.js.map +1 -0
  45. package/dist/agent/{dispositivosEnMaquina.js → dispositivos/dispositivosEnMaquina.js} +93 -6
  46. package/dist/agent/dispositivos/dispositivosEnMaquina.js.map +1 -0
  47. package/dist/agent/dispositivos/hotswap.js.map +1 -0
  48. package/dist/agent/dispositivos/instalacionEnMaquina.js.map +1 -0
  49. package/dist/agent/dispositivos/juezVisual.js +152 -0
  50. package/dist/agent/dispositivos/juezVisual.js.map +1 -0
  51. package/dist/agent/{lanzamientoEnMaquina.js → dispositivos/lanzamientoEnMaquina.js} +1 -1
  52. package/dist/agent/dispositivos/lanzamientoEnMaquina.js.map +1 -0
  53. package/dist/agent/{paqueteDelProyecto.js → dispositivos/paqueteDelProyecto.js} +5 -5
  54. package/dist/agent/dispositivos/paqueteDelProyecto.js.map +1 -0
  55. package/dist/agent/dispositivos/procesosEnMaquina.js.map +1 -0
  56. package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
  57. package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
  58. package/dist/agent/exportarPdf.js +140 -0
  59. package/dist/agent/exportarPdf.js.map +1 -0
  60. package/dist/agent/{arbolDeProyecto.js → grafo/arbolDeProyecto.js} +2 -2
  61. package/dist/agent/grafo/arbolDeProyecto.js.map +1 -0
  62. package/dist/agent/{artefactosEnDisco.js → grafo/artefactosEnDisco.js} +2 -2
  63. package/dist/agent/grafo/artefactosEnDisco.js.map +1 -0
  64. package/dist/agent/{busquedaRegex.js → grafo/busquedaRegex.js} +41 -1
  65. package/dist/agent/grafo/busquedaRegex.js.map +1 -0
  66. package/dist/agent/grafo/criticaVisual.js +115 -0
  67. package/dist/agent/grafo/criticaVisual.js.map +1 -0
  68. package/dist/agent/grafo/excluirTools.js +82 -0
  69. package/dist/agent/grafo/excluirTools.js.map +1 -0
  70. package/dist/agent/{memoriaDeProyecto.js → grafo/memoriaDeProyecto.js} +1 -1
  71. package/dist/agent/grafo/memoriaDeProyecto.js.map +1 -0
  72. package/dist/agent/grafo/navegacionXone.js +231 -0
  73. package/dist/agent/grafo/navegacionXone.js.map +1 -0
  74. package/dist/agent/{perfiles.js → grafo/perfiles.js} +96 -10
  75. package/dist/agent/grafo/perfiles.js.map +1 -0
  76. package/dist/agent/{proyecto.js → grafo/proyecto.js} +274 -24
  77. package/dist/agent/grafo/proyecto.js.map +1 -0
  78. package/dist/agent/grafo/skills.js +425 -0
  79. package/dist/agent/grafo/skills.js.map +1 -0
  80. package/dist/agent/grafo/xoneAgent.js +566 -0
  81. package/dist/agent/grafo/xoneAgent.js.map +1 -0
  82. package/dist/agent/navegacion/estilosEnDisco.js +110 -0
  83. package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
  84. package/dist/agent/navegacion/hechosEnDisco.js +26 -0
  85. package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
  86. package/dist/agent/navegacion/indiceEnDisco.js +36 -0
  87. package/dist/agent/navegacion/indiceEnDisco.js.map +1 -0
  88. package/dist/agent/navegacion/modeloDeProyecto.js +228 -0
  89. package/dist/agent/navegacion/modeloDeProyecto.js.map +1 -0
  90. package/dist/agent/raizDelPaquete.js +56 -0
  91. package/dist/agent/raizDelPaquete.js.map +1 -0
  92. package/dist/agent/{checkpointer.js → sesiones/checkpointer.js} +1 -1
  93. package/dist/agent/sesiones/checkpointer.js.map +1 -0
  94. package/dist/agent/sesiones/git.js.map +1 -0
  95. package/dist/agent/{gitSync.js → sesiones/gitSync.js} +3 -3
  96. package/dist/agent/sesiones/gitSync.js.map +1 -0
  97. package/dist/agent/{sesionGit.js → sesiones/sesionGit.js} +2 -2
  98. package/dist/agent/sesiones/sesionGit.js.map +1 -0
  99. package/dist/agent/subagentes/agentesEnDisco.js +818 -0
  100. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -0
  101. package/dist/agent/subagentes/consumoExterno.js.map +1 -0
  102. package/dist/agent/subagentes/escrituraDeCodex.js.map +1 -0
  103. package/dist/agent/{escrituraDeOpencode.js → subagentes/escrituraDeOpencode.js} +1 -1
  104. package/dist/agent/subagentes/escrituraDeOpencode.js.map +1 -0
  105. package/dist/agent/{escrituraExterna.js → subagentes/escrituraExterna.js} +59 -6
  106. package/dist/agent/subagentes/escrituraExterna.js.map +1 -0
  107. package/dist/agent/subagentes/pluginDeSkills.js +99 -0
  108. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
  109. package/dist/agent/subagentes/subagenteCodex.js.map +1 -0
  110. package/dist/agent/{subagenteExterno.js → subagentes/subagenteExterno.js} +48 -0
  111. package/dist/agent/subagentes/subagenteExterno.js.map +1 -0
  112. package/dist/agent/{subagenteOpencode.js → subagentes/subagenteOpencode.js} +53 -1
  113. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -0
  114. package/dist/agent/{aumentador.js → tareas/aumentador.js} +2 -2
  115. package/dist/agent/tareas/aumentador.js.map +1 -0
  116. package/dist/agent/{juezDeTarea.js → tareas/juezDeTarea.js} +6 -6
  117. package/dist/agent/tareas/juezDeTarea.js.map +1 -0
  118. package/dist/agent/{tareasEnDisco.js → tareas/tareasEnDisco.js} +53 -4
  119. package/dist/agent/tareas/tareasEnDisco.js.map +1 -0
  120. package/dist/agent/{diagnosticoDeTools.js → turno/diagnosticoDeTools.js} +5 -1
  121. package/dist/agent/turno/diagnosticoDeTools.js.map +1 -0
  122. package/dist/agent/{guionizado.js → turno/guionizado.js} +1 -1
  123. package/dist/agent/turno/guionizado.js.map +1 -0
  124. package/dist/agent/turno/informeDeTraza.js +245 -0
  125. package/dist/agent/turno/informeDeTraza.js.map +1 -0
  126. package/dist/agent/turno/inspectorDePrompt.js +205 -0
  127. package/dist/agent/turno/inspectorDePrompt.js.map +1 -0
  128. package/dist/agent/{instantanea.js → turno/instantanea.js} +7 -2
  129. package/dist/agent/turno/instantanea.js.map +1 -0
  130. package/dist/agent/{interrupts.js → turno/interrupts.js} +2 -2
  131. package/dist/agent/turno/interrupts.js.map +1 -0
  132. package/dist/agent/turno/mensajes.js.map +1 -0
  133. package/dist/agent/turno/normalizar.js.map +1 -0
  134. package/dist/agent/{puente.js → turno/puente.js} +71 -4
  135. package/dist/agent/turno/puente.js.map +1 -0
  136. package/dist/agent/turno/resumenDeContexto.js +260 -0
  137. package/dist/agent/turno/resumenDeContexto.js.map +1 -0
  138. package/dist/agent/{resumenDeTool.js → turno/resumenDeTool.js} +33 -0
  139. package/dist/agent/turno/resumenDeTool.js.map +1 -0
  140. package/dist/agent/turno/rubrica.js +91 -0
  141. package/dist/agent/turno/rubrica.js.map +1 -0
  142. package/dist/agent/turno/textoDeTool.js.map +1 -0
  143. package/dist/agent/{turnoReal.js → turno/turnoReal.js} +210 -25
  144. package/dist/agent/turno/turnoReal.js.map +1 -0
  145. package/dist/agent/turno/verificador.js.map +1 -0
  146. package/dist/cli/config.js +1 -1
  147. package/dist/cli/config.js.map +1 -1
  148. package/dist/cli/consola.js +72 -9
  149. package/dist/cli/consola.js.map +1 -1
  150. package/dist/cli/describe.js +2 -2
  151. package/dist/cli/describe.js.map +1 -1
  152. package/dist/cli/doctor.js +1 -1
  153. package/dist/cli/doctor.js.map +1 -1
  154. package/dist/cli/fuentesDeDisco.js +29 -0
  155. package/dist/cli/fuentesDeDisco.js.map +1 -0
  156. package/dist/cli/main.js +87 -31
  157. package/dist/cli/main.js.map +1 -1
  158. package/dist/cli/run.js +40 -10
  159. package/dist/cli/run.js.map +1 -1
  160. package/dist/cli/traza.js +48 -0
  161. package/dist/cli/traza.js.map +1 -0
  162. package/dist/cli/tui/correrTui.js +4 -4
  163. package/dist/cli/tui/correrTui.js.map +1 -1
  164. package/dist/cli/verify.js +1 -1
  165. package/dist/cli/verify.js.map +1 -1
  166. package/dist/core/actos.js +1 -1
  167. package/dist/core/adjuntos.js +2 -2
  168. package/dist/core/agentes.js +247 -7
  169. package/dist/core/agentes.js.map +1 -1
  170. package/dist/core/artefactos.js +4 -4
  171. package/dist/core/config.js +1 -1
  172. package/dist/core/contextos.js +12 -1
  173. package/dist/core/contextos.js.map +1 -1
  174. package/dist/core/descargas.js +1 -1
  175. package/dist/core/dispositivos.js +53 -3
  176. package/dist/core/dispositivos.js.map +1 -1
  177. package/dist/core/esfuerzo.js +141 -0
  178. package/dist/core/esfuerzo.js.map +1 -0
  179. package/dist/core/esqueleto.js +1 -1
  180. package/dist/core/estilos.js +78 -0
  181. package/dist/core/estilos.js.map +1 -0
  182. package/dist/core/exportacion.js +140 -0
  183. package/dist/core/exportacion.js.map +1 -0
  184. package/dist/core/hechosDelProyecto.js +87 -0
  185. package/dist/core/hechosDelProyecto.js.map +1 -0
  186. package/dist/core/logDeDispositivo.js +143 -0
  187. package/dist/core/logDeDispositivo.js.map +1 -0
  188. package/dist/core/modelos.js +48 -3
  189. package/dist/core/modelos.js.map +1 -1
  190. package/dist/core/mudanzaDeWorkspace.js +93 -0
  191. package/dist/core/mudanzaDeWorkspace.js.map +1 -0
  192. package/dist/core/navegacion.js +134 -0
  193. package/dist/core/navegacion.js.map +1 -0
  194. package/dist/core/notify.js +5 -0
  195. package/dist/core/notify.js.map +1 -1
  196. package/dist/core/planDeSubida.js +1 -1
  197. package/dist/core/planes.js +70 -0
  198. package/dist/core/planes.js.map +1 -0
  199. package/dist/core/ports.js +4 -0
  200. package/dist/core/ports.js.map +1 -1
  201. package/dist/core/rubrica.js +90 -0
  202. package/dist/core/rubrica.js.map +1 -0
  203. package/dist/core/selectorDeCarpeta.js +87 -0
  204. package/dist/core/selectorDeCarpeta.js.map +1 -0
  205. package/dist/core/settings.js +56 -7
  206. package/dist/core/settings.js.map +1 -1
  207. package/dist/core/shellDeAgente.js +144 -0
  208. package/dist/core/shellDeAgente.js.map +1 -0
  209. package/dist/core/skills.js +158 -0
  210. package/dist/core/skills.js.map +1 -0
  211. package/dist/core/topeAgotado.js +69 -0
  212. package/dist/core/topeAgotado.js.map +1 -0
  213. package/dist/core/zipDeSkill.js +107 -0
  214. package/dist/core/zipDeSkill.js.map +1 -0
  215. package/dist/web/servidor/arranque.js +853 -54
  216. package/dist/web/servidor/arranque.js.map +1 -1
  217. package/dist/web/servidor/corredorDeTareas.js +3 -3
  218. package/dist/web/servidor/corredorDeTareas.js.map +1 -1
  219. package/dist/web/servidor/sesiones.js +34 -6
  220. package/dist/web/servidor/sesiones.js.map +1 -1
  221. package/dist/web/servidor/transporte.js +1 -1
  222. package/dist/web/servidor/transporte.js.map +1 -1
  223. package/dist/web/servidor/vestibulo.js +62 -13
  224. package/dist/web/servidor/vestibulo.js.map +1 -1
  225. package/package.json +7 -4
  226. package/skills/archify/SKILL.md +25 -2
  227. package/skills/artifacts-builder/SKILL.md +23 -0
  228. package/skills/xone-hotswap/SKILL.md +96 -0
  229. package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
  230. package/skills/xone-hotswap/scripts/xone-arrancar-ios +85 -0
  231. package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
  232. package/skills/xone-hotswap/scripts/xone-desplegar-android +439 -0
  233. package/skills/xone-hotswap/scripts/xone-hotswap +201 -0
  234. package/skills/xone-hotswap/scripts/xone-log-android +102 -0
  235. package/skills/xone-hotswap/scripts/xone-reiniciar-android +132 -0
  236. package/skills/xone-plan-builder/SKILL.md +22 -4
  237. package/skills/xone-spec-builder/SKILL.md +37 -7
  238. package/apps/web/dist/assets/index-CFYAoHdw.js +0 -477
  239. package/dist/agent/agentesEnDisco.js +0 -539
  240. package/dist/agent/agentesEnDisco.js.map +0 -1
  241. package/dist/agent/arbolDeProyecto.js.map +0 -1
  242. package/dist/agent/artefactosEnDisco.js.map +0 -1
  243. package/dist/agent/aumentador.js.map +0 -1
  244. package/dist/agent/authEnDisco.js.map +0 -1
  245. package/dist/agent/busquedaRegex.js.map +0 -1
  246. package/dist/agent/catalogoModelos.js.map +0 -1
  247. package/dist/agent/checkpointer.js.map +0 -1
  248. package/dist/agent/cloudstudioClient.js.map +0 -1
  249. package/dist/agent/cloudstudioMcp.js.map +0 -1
  250. package/dist/agent/configEnDisco.js.map +0 -1
  251. package/dist/agent/consumoExterno.js.map +0 -1
  252. package/dist/agent/crearProyecto.js.map +0 -1
  253. package/dist/agent/descarga.js.map +0 -1
  254. package/dist/agent/diagnosticoDeTools.js.map +0 -1
  255. package/dist/agent/dispositivosEnMaquina.js.map +0 -1
  256. package/dist/agent/entorno.js.map +0 -1
  257. package/dist/agent/escrituraDeCodex.js.map +0 -1
  258. package/dist/agent/escrituraDeOpencode.js.map +0 -1
  259. package/dist/agent/escrituraExterna.js.map +0 -1
  260. package/dist/agent/gemini.js.map +0 -1
  261. package/dist/agent/git.js.map +0 -1
  262. package/dist/agent/gitSync.js.map +0 -1
  263. package/dist/agent/guionizado.js.map +0 -1
  264. package/dist/agent/hotswap.js.map +0 -1
  265. package/dist/agent/instalacionEnMaquina.js.map +0 -1
  266. package/dist/agent/instantanea.js.map +0 -1
  267. package/dist/agent/interrupts.js.map +0 -1
  268. package/dist/agent/juezDeTarea.js.map +0 -1
  269. package/dist/agent/lanzamientoEnMaquina.js.map +0 -1
  270. package/dist/agent/manifiesto.js.map +0 -1
  271. package/dist/agent/memoriaDeProyecto.js.map +0 -1
  272. package/dist/agent/mensajes.js.map +0 -1
  273. package/dist/agent/modelos.js +0 -130
  274. package/dist/agent/modelos.js.map +0 -1
  275. package/dist/agent/modelosDeMotor.js.map +0 -1
  276. package/dist/agent/normalizar.js.map +0 -1
  277. package/dist/agent/paqueteDelProyecto.js.map +0 -1
  278. package/dist/agent/perfiles.js.map +0 -1
  279. package/dist/agent/persona.js.map +0 -1
  280. package/dist/agent/procesosEnMaquina.js.map +0 -1
  281. package/dist/agent/proyecto.js.map +0 -1
  282. package/dist/agent/puente.js.map +0 -1
  283. package/dist/agent/resumenDeContexto.js +0 -30
  284. package/dist/agent/resumenDeContexto.js.map +0 -1
  285. package/dist/agent/resumenDeTool.js.map +0 -1
  286. package/dist/agent/sesionGit.js.map +0 -1
  287. package/dist/agent/settingsEnDisco.js.map +0 -1
  288. package/dist/agent/skills.js +0 -65
  289. package/dist/agent/skills.js.map +0 -1
  290. package/dist/agent/subagenteCodex.js.map +0 -1
  291. package/dist/agent/subagenteExterno.js.map +0 -1
  292. package/dist/agent/subagenteOpencode.js.map +0 -1
  293. package/dist/agent/subida.js.map +0 -1
  294. package/dist/agent/tareasEnDisco.js.map +0 -1
  295. package/dist/agent/textoDeTool.js.map +0 -1
  296. package/dist/agent/turnoReal.js.map +0 -1
  297. package/dist/agent/verificador.js.map +0 -1
  298. package/dist/agent/versionEnDisco.js.map +0 -1
  299. package/dist/agent/xoneAgent.js +0 -347
  300. package/dist/agent/xoneAgent.js.map +0 -1
  301. package/dist/agent/zip.js.map +0 -1
  302. /package/dist/agent/{manifiesto.js → cloudstudio/manifiesto.js} +0 -0
  303. /package/dist/agent/{entorno.js → config/entorno.js} +0 -0
  304. /package/dist/agent/{gemini.js → config/gemini.js} +0 -0
  305. /package/dist/agent/{hotswap.js → dispositivos/hotswap.js} +0 -0
  306. /package/dist/agent/{instalacionEnMaquina.js → dispositivos/instalacionEnMaquina.js} +0 -0
  307. /package/dist/agent/{procesosEnMaquina.js → dispositivos/procesosEnMaquina.js} +0 -0
  308. /package/dist/agent/{git.js → sesiones/git.js} +0 -0
  309. /package/dist/agent/{consumoExterno.js → subagentes/consumoExterno.js} +0 -0
  310. /package/dist/agent/{escrituraDeCodex.js → subagentes/escrituraDeCodex.js} +0 -0
  311. /package/dist/agent/{subagenteCodex.js → subagentes/subagenteCodex.js} +0 -0
  312. /package/dist/agent/{mensajes.js → turno/mensajes.js} +0 -0
  313. /package/dist/agent/{normalizar.js → turno/normalizar.js} +0 -0
  314. /package/dist/agent/{textoDeTool.js → turno/textoDeTool.js} +0 -0
  315. /package/dist/agent/{verificador.js → turno/verificador.js} +0 -0
@@ -0,0 +1,818 @@
1
+ /**
2
+ * Los subagentes en disco: `<base>/.xonecode/agentes/<nombre>.md`, uno por fichero.
3
+ *
4
+ * Ficheros y no una entrada en `config.json` por tres razones. El cuerpo son
5
+ * INSTRUCCIONES, o sea prosa larga, y la prosa dentro de un JSON se vuelve inmanejable en
6
+ * cuanto pasa de dos frases. Es la convención que ya conoce quien viene de Claude Code
7
+ * (`.claude/agents/*.md`), así que el fichero se lee sin explicar nada. Y se edita a mano y
8
+ * se diffea, que es lo que va a pasar de verdad con un prompt que se afina.
9
+ *
10
+ * `.xonecode/` hereda además dos protecciones que aquí importan: está denegada entera al
11
+ * agente (`permisosDe`) y **no sube nunca** a CloudStudio. Un subagente es configuración de
12
+ * la herramienta, no del proyecto XOne — no tiene por qué acabar en Studio.
13
+ *
14
+ * Dos sitios, con la misma precedencia que los modelos: el GLOBAL (`~/.xonecode/agentes/`)
15
+ * y el del PROYECTO, y el de proyecto gana. Un «revisor de XOne» se quiere en todos los
16
+ * proyectos; un «experto en esta app», solo en uno.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { escribirAgente, fusionarAgentes, leerAgente, motivoDeNombreInaceptable, nombreSugerido, } from "../../core/agentes.js";
23
+ import { segmentoSeguro } from "../../core/settings.js";
24
+ import { NOMBRE_CARPETA } from "../config/configEnDisco.js";
25
+ const CARPETA = "agentes";
26
+ /** La carpeta de agentes de una raíz cualquiera. */
27
+ export function rutaDeAgentes(base) {
28
+ return join(base, NOMBRE_CARPETA, CARPETA);
29
+ }
30
+ /** La global. `homedir()` en el momento de la llamada, como el resto del repo: cachearla en
31
+ * una constante de módulo la congelaría para los tests que la cambian. */
32
+ export function rutaGlobalDeAgentes() {
33
+ return join(homedir(), NOMBRE_CARPETA, CARPETA);
34
+ }
35
+ /**
36
+ * Lee una carpeta. Un fichero que no se puede leer o que no valida NO tumba la carga: se
37
+ * salta, se apunta el motivo y los demás siguen — es la misma postura que `reabrirSesion`
38
+ * con una línea corrupta del `.jsonl`, y la que evita que un `.md` a medio escribir deje la
39
+ * consola sin ningún subagente.
40
+ */
41
+ export function leerCarpetaDeAgentes(carpeta, origen) {
42
+ if (!existsSync(carpeta))
43
+ return { agentes: [], problemas: [] };
44
+ const agentes = [];
45
+ const problemas = [];
46
+ for (const fichero of readdirSync(carpeta).sort()) {
47
+ if (!fichero.endsWith(".md"))
48
+ continue;
49
+ const nombre = fichero.slice(0, -3);
50
+ let contenido;
51
+ try {
52
+ contenido = readFileSync(join(carpeta, fichero), "utf8");
53
+ }
54
+ catch (e) {
55
+ problemas.push(`${fichero}: no se pudo leer (${e.message})`);
56
+ continue;
57
+ }
58
+ const r = leerAgente(nombre, contenido, origen);
59
+ if ("error" in r) {
60
+ problemas.push(`${fichero}: ${r.error}`);
61
+ continue;
62
+ }
63
+ /**
64
+ * Un nombre que no es un slug se DICE y el agente se CARGA.
65
+ *
66
+ * Rechazarlo aquí haría desaparecer un subagente que funciona, que es lo contrario de lo
67
+ * que hace todo este módulo — y el usuario no tendría ni el nombre para buscarlo. Quien
68
+ * lo rechaza es el guardado (`arranque.ts#atenderAgente`), que es el único momento con
69
+ * alguien delante para arreglarlo. El aviso va por `problemas`, y ahí sí encaja: esto no
70
+ * es un estado del agente, es trabajo pendiente sobre su fichero.
71
+ *
72
+ * Y lleva el nombre que SÍ valdría, porque un aviso que no se puede obedecer no sirve:
73
+ * «renómbralo a `documentador`» se arregla en el mismo formulario donde se lee.
74
+ */
75
+ const malNombre = motivoDeNombreInaceptable(nombre);
76
+ if (malNombre !== undefined) {
77
+ const sugerido = nombreSugerido(nombre);
78
+ problemas.push(`${fichero}: el nombre ${malNombre}` +
79
+ (sugerido === undefined ? ". Renómbralo." : `. Renómbralo a \`${sugerido}\`.`));
80
+ }
81
+ agentes.push(r.agente);
82
+ }
83
+ return { agentes, problemas };
84
+ }
85
+ /**
86
+ * Los agentes en vigor para un proyecto: los globales, pisados por los del proyecto.
87
+ *
88
+ * `raizDelProyecto` es opcional porque el vestíbulo existe ANTES de que haya proyecto
89
+ * abierto (`web/servidor/vestibulo.ts`) y la ventana de ajustes se abre desde ahí: sin
90
+ * raíz se contestan los globales, que es la verdad, en vez de una lista vacía.
91
+ */
92
+ export function cargarAgentes(raizDelProyecto) {
93
+ // La siembra se hace AQUÍ, y no en el arranque de cada piel. Medido: estaba en
94
+ // `main.ts#entrarEnConsola` y la rama web devuelve antes de llegar ahí, así que
95
+ // `npm run web` no sembraba nada — la consola arrancaba sin un solo subagente y el
96
+ // orquestador sin nadie a quien delegar, sin que nada diera error. Colgarlo del cargador
97
+ // lo hace imposible de olvidar: quien necesita agentes los pide por aquí, y por
98
+ // construcción hay algo que leer. Es idempotente y no hace nada si la carpeta existe.
99
+ const { desactualizados, retirados } = sembrarAgentes();
100
+ const global = leerCarpetaDeAgentes(rutaGlobalDeAgentes(), "global");
101
+ const proyecto = raizDelProyecto === undefined
102
+ ? { agentes: [], problemas: [] }
103
+ : leerCarpetaDeAgentes(rutaDeAgentes(raizDelProyecto), "proyecto");
104
+ // Los que se quedaron atrás NO van a `problemas`, y eso es un cambio medido: iban, y por
105
+ // el mismo canal que un `.md` que no carga, así que la consola pintaba en rojo y arriba del
106
+ // todo dos agentes que están perfectamente. Y su frase —«bórralo si quieres el nuevo»—
107
+ // mandaba a usar una escapatoria que no existe: borrar uno de serie no lo resiembra (la
108
+ // marca recuerda que se entregó), así que dejaba sin ninguno de los dos y para siempre.
109
+ // Ahora el estado viaja POR AGENTE (`marcarSemilla`) y se dice en su tarjeta, que es donde
110
+ // está el botón que lo arregla — `restaurarAgente`. `problemas` vuelve a significar solo
111
+ // «este fichero no se pudo cargar»; comprobado que nadie más lo lee (`turnoReal` y los
112
+ // evals se quedan con `.agentes`).
113
+ // Y los renombrados, por el mismo canal y por la misma razón: el especialista cambia de
114
+ // nombre (o se va con él), y enterarse por la lista sin que nadie lo explique es la clase de
115
+ // sorpresa silenciosa que este módulo existe para no dar. El que se retiró se dice UNA vez
116
+ // —la clave se va con el fichero—; el que se respeta, cada arranque, porque queda decidir.
117
+ const renombrados = retirados.map(({ nombre, ahoraSeLlama, borrado }) => borrado
118
+ ? `${nombre}.md: se ha retirado —ese agente se llama ahora \`${ahoraSeLlama}\`— y el nuevo ya está sembrado.`
119
+ : `${nombre}.md: ese agente se llama ahora \`${ahoraSeLlama}\`, y el tuyo se respeta. Bórralo si quieres quedarte solo con el de serie.`);
120
+ return {
121
+ agentes: marcarSemilla(fusionarAgentes(global.agentes, proyecto.agentes), desactualizados),
122
+ problemas: [...renombrados, ...global.problemas, ...proyecto.problemas],
123
+ };
124
+ }
125
+ /** ¿Es uno de los que trae xonecode? La guarda del `borrar`, y la lista de la pantalla. */
126
+ export function esDeSerie(nombre) {
127
+ return AGENTES_DE_SERIE.some((a) => a.nombre === nombre);
128
+ }
129
+ /**
130
+ * De quién es cada `.md`: ausente si lo escribió el usuario, `intacta`/`modificada` si es
131
+ * uno de los nuestros. Ver `AgenteCargado` para los tres estados y qué se hace con cada uno.
132
+ *
133
+ * Pura y exportada a propósito, no compuesta dentro de `cargarAgentes`: es el patrón de
134
+ * fallo de esta arquitectura —una regla que vive en un cierre que todos los tests doblan—, y
135
+ * la trampa de abajo es justo la que se queda sin probar así.
136
+ *
137
+ * **La regla es «de serie Y del GLOBAL», y el segundo requisito no es decorativo.** La
138
+ * siembra solo toca el global, así que un `docs.md` en `.xonecode/agentes/` DEL PROYECTO lo
139
+ * escribió el usuario aunque se llame igual que uno nuestro. Con `nombre ∈ AGENTES_DE_SERIE`
140
+ * a secas se quedaría sin botón de borrar —un fichero suyo que no puede borrar— y con un
141
+ * «Restaurar el de serie» que le pisaría el suyo con el global.
142
+ */
143
+ export function marcarSemilla(agentes, desactualizados) {
144
+ return agentes.map((a) => a.origen === "global" && esDeSerie(a.nombre)
145
+ ? { ...a, semilla: desactualizados.includes(a.nombre) ? "modificada" : "intacta" }
146
+ : a);
147
+ }
148
+ /**
149
+ * Escribe un agente.
150
+ *
151
+ * `segmentoSeguro` por lo mismo que en `sesiones.ts`: el nombre llega del cliente por HTTP,
152
+ * y un `../../.env` compondría una ruta fuera de la carpeta. Misma función que usa
153
+ * `rutaDeWorkspace`, no una copia — dos guardas es cómo una se corrige y la otra no. LANZA
154
+ * en vez de limpiar, y está bien: limpiar el nombre hasta hacerlo válido guardaría el
155
+ * agente con uno que nadie pidió, y quien lo mandó creería que se llama de otra forma.
156
+ *
157
+ * La validación va ANTES del `mkdirSync`, y no es cosmético: al revés, un nombre rechazado
158
+ * dejaba la carpeta creada de todas formas — y la carpeta es justo la marca de «ya se
159
+ * sembró» (`sembrarAgentes`), así que un intento fallido en el global habría impedido para
160
+ * siempre que se sembraran los cuatro de serie.
161
+ */
162
+ export function guardarAgente(base, agente) {
163
+ const seguro = segmentoSeguro(agente.nombre, "nombre de agente");
164
+ const carpeta = rutaDeAgentes(base);
165
+ mkdirSync(carpeta, { recursive: true });
166
+ writeFileSync(join(carpeta, `${seguro}.md`), escribirAgente({ ...agente, nombre: seguro }), "utf8");
167
+ }
168
+ /**
169
+ * Devuelve un agente de serie a como lo entregamos, pisando lo que el usuario tuviera.
170
+ *
171
+ * Es lo que ocupa el sitio del borrado en un agente sembrado, y existe porque el borrado
172
+ * NO hacía lo que la consola prometía: la marca recuerda que se entregó, así que
173
+ * `sembrarAgentes` no lo resiembra (ver «uno BORRADO no se resucita») — o sea que el
174
+ * «bórralo si quieres el nuevo» dejaba sin ninguno de los dos y para siempre.
175
+ *
176
+ * **No escribe la marca, y es deliberado.** La reanota la siembra siguiente, que corre ANTES
177
+ * de cualquier lectura —`cargarAgentes` la llama primero— y que ya sabe reconocer su propio
178
+ * hash. Escribirla aquí sería un segundo sitio donde decidir sobre la marca, y el único que
179
+ * podría resucitar un agente que el usuario borró a propósito: con una marca ilegible, este
180
+ * camino la reescribiría con una sola clave y las demás pasarían por «nunca entregadas».
181
+ *
182
+ * Devuelve si pudo, como `borrarAgente`: de algo que no es de serie no hay versión nuestra
183
+ * que poner, y la interfaz no puede decir «restaurado» de eso.
184
+ */
185
+ export function restaurarAgente(base, nombre) {
186
+ const agente = AGENTES_DE_SERIE.find((a) => a.nombre === nombre);
187
+ if (agente === undefined)
188
+ return false;
189
+ const carpeta = rutaDeAgentes(base);
190
+ mkdirSync(carpeta, { recursive: true });
191
+ writeFileSync(join(carpeta, `${agente.nombre}.md`), escribirAgente(agente), "utf8");
192
+ return true;
193
+ }
194
+ /**
195
+ * Renombra un subagente, escribiendo a la vez lo que se haya cambiado de él.
196
+ *
197
+ * **`renameSync` y LUEGO escribir**, nunca escribir y luego borrar: un fallo entre los dos
198
+ * pasos deja UN fichero —el renombrado, con el contenido de antes—, nunca dos con el mismo
199
+ * prompt ni cero. Al revés, una excepción entre el `write` y el `rm` deja los DOS, que es
200
+ * justo lo que el campo deshabilitado del formulario evitaba: dos subagentes con la misma
201
+ * descripción y ninguna forma de saber a cuál delega el orquestador.
202
+ *
203
+ * Las dos cosas en el mismo paso porque el formulario permite cambiar el nombre Y el resto en
204
+ * la misma pulsación; guardar solo una dejaría la pantalla mintiendo.
205
+ *
206
+ * **El destino ocupado es un NO**, sea de serie o del usuario: sin esta guarda, renombrar
207
+ * `advisor` a `docs` se llevaba por delante el `docs.md` sembrado —o el otro subagente— sin
208
+ * decir nada. Un caso especial para los de serie diría un motivo menos cierto.
209
+ *
210
+ * **Pero «existe» no es «es otro fichero», y eso está MEDIDO en una máquina de verdad.** APFS
211
+ * es insensible a mayúsculas, así que con un `Documentador.md` en disco el `existsSync` de
212
+ * `documentador.md` contesta SÍ —es el mismo fichero— y esta guarda rechazaba justo el arreglo
213
+ * que el aviso del cargador propone, que es el renombrado más común de todos. Se compara por
214
+ * INODO (`dev` + `ino`): si el destino ES el origen, no hay nada ocupado. En un sistema
215
+ * sensible a mayúsculas son dos ficheros con dos inodos, así que la comparación sigue diciendo
216
+ * la verdad ahí — y si el otro existe de verdad, se sigue rechazando.
217
+ *
218
+ * Que un de serie NO se pueda renombrar no se decide aquí: esto mueve un fichero, y la regla
219
+ * del producto vive donde ya vive la de que no se borra (`arranque.ts#atenderAgente`). Dos
220
+ * sitios donde decidir lo mismo es cómo uno de los dos se queda sin la regla.
221
+ */
222
+ export function renombrarAgente(base, viejo, agente) {
223
+ // Antes de mover nada: el nombre viene del cliente por HTTP, y `segmentoSeguro` LANZA en
224
+ // vez de limpiarlo — limpiarlo guardaría el agente con un nombre que nadie pidió.
225
+ const seguro = segmentoSeguro(agente.nombre, "nombre de agente");
226
+ const carpeta = rutaDeAgentes(base);
227
+ const origen = join(carpeta, `${segmentoSeguro(viejo, "nombre de agente")}.md`);
228
+ const destino = join(carpeta, `${seguro}.md`);
229
+ if (!existsSync(origen))
230
+ return "sin-origen";
231
+ if (existsSync(destino) && !elMismoFichero(origen, destino))
232
+ return "destino-ocupado";
233
+ renameSync(origen, destino);
234
+ writeFileSync(destino, escribirAgente({ ...agente, nombre: seguro }), "utf8");
235
+ return "hecho";
236
+ }
237
+ /**
238
+ * ¿Las dos rutas son el MISMO fichero? Por `dev` + `ino`, no por texto.
239
+ *
240
+ * Por texto no se puede: en un sistema insensible a mayúsculas `Documentador.md` y
241
+ * `documentador.md` son el mismo y sus nombres no se parecen bajo `===`; y comparar en
242
+ * minúsculas mentiría en un sistema sensible, donde sí son dos. El inodo contesta la pregunta
243
+ * de verdad en los dos. Lo que no se puede mirar se da por DISTINTO, que es el lado que no
244
+ * pisa nada.
245
+ */
246
+ function elMismoFichero(a, b) {
247
+ try {
248
+ const ea = statSync(a);
249
+ const eb = statSync(b);
250
+ return ea.dev === eb.dev && ea.ino === eb.ino;
251
+ }
252
+ catch {
253
+ return false;
254
+ }
255
+ }
256
+ /** Borra uno. Devuelve si existía: la interfaz no puede decir «borrado» de algo que no estaba. */
257
+ export function borrarAgente(base, nombre) {
258
+ const ruta = join(rutaDeAgentes(base), `${segmentoSeguro(nombre, "nombre de agente")}.md`);
259
+ if (!existsSync(ruta))
260
+ return false;
261
+ rmSync(ruta);
262
+ return true;
263
+ }
264
+ /**
265
+ * Los especialistas de serie, sembrados en el GLOBAL.
266
+ *
267
+ * Dejan de ser un `Record` a fuego en `agent/grafo/perfiles.ts` y pasan a ser los mismos ficheros
268
+ * que puede escribir el usuario. Lo pidió él, y además arregla algo que estaba señalado
269
+ * como provisional en `xoneAgent.ts#promptDe` desde que se escribió: los prompts de los
270
+ * especialistas vivían en código con un `nombre === "planner"` dentro para las
271
+ * particularidades de uno de ellos.
272
+ *
273
+ * Lo que sigue siendo intocable, y por lo que la regla anterior existía: **el prompt que el
274
+ * usuario afina no se pisa, y el agente que borra no se resucita.** Lo que cambió es cómo
275
+ * se sabe cuál es cuál — ver `sembrarAgentes`.
276
+ */
277
+ /** Lo que la marca guarda de cada agente sembrado: el hash de lo que ESCRIBIMOS nosotros. */
278
+ const AJENO = "ajeno";
279
+ /** El nombre de la marca. Empieza por punto y no acaba en `.md`: el cargador la ignora. */
280
+ export const FICHERO_DE_SEMILLA = ".semilla.json";
281
+ function rutaDeSemilla(carpeta) {
282
+ return join(carpeta, FICHERO_DE_SEMILLA);
283
+ }
284
+ function huella(contenido) {
285
+ return createHash("sha256").update(contenido, "utf8").digest("hex").slice(0, 16);
286
+ }
287
+ /** La marca en disco. Ausente o rota = no hay marca: se ADOPTA lo que haya (ver abajo). */
288
+ function leerSemilla(carpeta) {
289
+ const ruta = rutaDeSemilla(carpeta);
290
+ if (!existsSync(ruta))
291
+ return undefined;
292
+ try {
293
+ const bruto = JSON.parse(readFileSync(ruta, "utf8"));
294
+ if (typeof bruto !== "object" || bruto === null || Array.isArray(bruto))
295
+ return undefined;
296
+ const salida = {};
297
+ for (const [nombre, valor] of Object.entries(bruto)) {
298
+ if (typeof valor === "string")
299
+ salida[nombre] = valor;
300
+ }
301
+ return salida;
302
+ }
303
+ catch {
304
+ // Una marca rota se trata como ausente y se REESCRIBE adoptando lo que hay. Es lo
305
+ // conservador: lo contrario —darla por vacía y sembrar— pisaría ficheros del usuario.
306
+ return undefined;
307
+ }
308
+ }
309
+ function escribirSemilla(carpeta, marca) {
310
+ writeFileSync(rutaDeSemilla(carpeta), JSON.stringify(marca, null, 2) + "\n", "utf8");
311
+ }
312
+ /**
313
+ * Los renombrados: nombre viejo → nombre nuevo.
314
+ *
315
+ * Hace falta porque la marca guarda el hash **por nombre**: renombrar deja una clave que ya no
316
+ * es de serie, y `sembrarAgentes` no la miraba — quien ya tuviera `probador.md` se quedaba con
317
+ * los DOS especialistas, uno de ellos sin mantenimiento.
318
+ *
319
+ * La alternativa era retirar toda clave desconocida cuyo hash fuera el nuestro. Funcionaría
320
+ * hoy y sería una trampa mañana: borraría cualquier entrada rara que un fallo dejara en la
321
+ * marca. Aquí solo se retira lo que consta que renombramos.
322
+ */
323
+ const RENOMBRADOS = {
324
+ // Los cinco de serie pasaron a `<rol>-xone`, en inglés. El sufijo no es decoración: estos
325
+ // nombres viajan como `subagent_type` a los motores externos, donde el hijo tiene sus
326
+ // propios agentes, y ahí es lo que los distingue.
327
+ docs: "consultant-xone",
328
+ planner: "analyst-xone",
329
+ dev: "developer-xone",
330
+ mockup: "designer-xone",
331
+ "xone-device-tester": "device-controller",
332
+ /**
333
+ * Y el de antes se ACTUALIZA, no se deja: apuntaba a `xone-device-tester`, que ya no es de
334
+ * serie, así que a quien conserve un `probador.md` afinado se le habría seguido diciendo
335
+ * que «ese agente se llama ahora `xone-device-tester`» — un nombre que no existe. Esto se
336
+ * lee de un solo salto, no como una cadena.
337
+ *
338
+ * Dos claves pueden apuntar al mismo nombre nuevo sin chocar: cada entrada se resuelve por
339
+ * separado contra el hash de SU fichero.
340
+ */
341
+ probador: "device-controller",
342
+ /**
343
+ * Y `tester-xone` se convirtió en `device-controller` cuando dejó de escribir pruebas para
344
+ * CONDUCIR el aparato: con `ejecucion: true` ya no es el que dice cómo se probaría, es el
345
+ * que lo hace. El nombre viejo describía lo que podía hacer cuando no tenía manos.
346
+ *
347
+ * **Se sale de la convención `<rol>-xone` a propósito**, y es decisión del usuario: lo que
348
+ * conduce no es propio de XOne —mañana es otro aparato con otra skill—, y el sufijo estaba
349
+ * para distinguir nuestros nombres de los agentes del hijo en un motor externo. Este no
350
+ * viaja a ninguno: con motor externo la ejecución no se concede (`puedeEjecutar`).
351
+ */
352
+ "tester-xone": "device-controller",
353
+ };
354
+ /**
355
+ * Siembra los agentes de serie, y ACTUALIZA los que nadie ha tocado.
356
+ *
357
+ * La regla anterior era «la carpeta es la marca»: si existía, no se escribía nada nunca más.
358
+ * Respetaba el prompt afinado por el usuario —que es lo que había que respetar— pero eligió
359
+ * un cuerno del dilema y el otro acabó mordiendo: **ningún agente nuevo, y ninguna
360
+ * corrección a uno existente, alcanzaba a quien ya hubiera arrancado una vez**. Medido: el
361
+ * `docs.md` de un usuario llevaba semanas sin la consulta acotada, y el probador de
362
+ * dispositivos no le habría llegado jamás.
363
+ *
364
+ * Ahora la marca es un fichero, `.semilla.json`, con el HASH DE LO QUE ESCRIBIMOS NOSOTROS
365
+ * para cada agente. Con eso se distinguen los cuatro casos que antes eran uno solo:
366
+ *
367
+ * | en disco | en la marca | qué se hace |
368
+ * |---|---|---|
369
+ * | no está | no está | es un agente NUEVO: se escribe |
370
+ * | no está | está | lo BORRÓ el usuario: no se resucita |
371
+ * | está, y su hash es el nuestro | está | nadie lo tocó: se actualiza |
372
+ * | está, y su hash NO es el nuestro | cualquiera | es suyo: se deja, y se DICE |
373
+ *
374
+ * Y un quinto, que aparece el día que un agente se RENOMBRA y que no puede salir de ese bucle
375
+ * porque habla de una clave que ya no nombra a ninguno: ver `RENOMBRADOS` y `Retirado`.
376
+ *
377
+ * **Y una carpeta sin marca se ADOPTA, no se siembra.** Es la de quien ya venía de la regla
378
+ * vieja, y ahí no se puede saber qué borró a propósito: dar por nuevo lo que falta le
379
+ * resucitaría un agente que eliminó. Así que se anota lo que hay —como nuestro si coincide
380
+ * con la versión de serie de hoy, como `ajeno` si no— y no se escribe ningún `.md` esa vez.
381
+ * Desde la siguiente, todo lo de arriba funciona. El coste es una ronda de retraso para las
382
+ * instalaciones viejas; la alternativa es pisar o resucitar sin permiso.
383
+ *
384
+ * El fichero de marca empieza por punto y no acaba en `.md`, así que `leerCarpetaDeAgentes`
385
+ * ni lo mira — no hace falta excluirlo a mano en dos sitios.
386
+ */
387
+ export function sembrarAgentes(base = homedir()) {
388
+ const carpeta = rutaDeAgentes(base);
389
+ const escritos = [];
390
+ const desactualizados = [];
391
+ if (!existsSync(carpeta)) {
392
+ mkdirSync(carpeta, { recursive: true });
393
+ const marca = {};
394
+ for (const agente of AGENTES_DE_SERIE) {
395
+ const contenido = escribirAgente(agente);
396
+ writeFileSync(join(carpeta, `${agente.nombre}.md`), contenido, "utf8");
397
+ marca[agente.nombre] = huella(contenido);
398
+ escritos.push(agente.nombre);
399
+ }
400
+ escribirSemilla(carpeta, marca);
401
+ return { escritos, desactualizados, retirados: [] };
402
+ }
403
+ const previa = leerSemilla(carpeta);
404
+ const marca = { ...(previa ?? {}) };
405
+ /**
406
+ * Sin marca hay DOS carpetas distintas, y confundirlas cuesta caro en los dos sentidos.
407
+ *
408
+ * La de quien viene de la regla vieja tiene agentes dentro: ahí se adopta, porque no se
409
+ * puede saber qué borró a propósito. Pero una carpeta sin NINGUNO de los de serie no
410
+ * viene de ninguna siembra —la deja, por ejemplo, un `guardarAgente` con un nombre
411
+ * inválido— y adoptarla anotaría los cinco como entregados sin escribir uno solo: ese
412
+ * usuario se quedaría sin ningún subagente para siempre. Se siembra, que es lo que
413
+ * habría pasado si la carpeta no existiera.
414
+ */
415
+ const hayDeSerie = AGENTES_DE_SERIE.some((a) => existsSync(join(carpeta, `${a.nombre}.md`)));
416
+ const adoptando = previa === undefined && hayDeSerie;
417
+ for (const agente of AGENTES_DE_SERIE) {
418
+ const ruta = join(carpeta, `${agente.nombre}.md`);
419
+ const contenido = escribirAgente(agente);
420
+ const nuestro = huella(contenido);
421
+ let enDisco;
422
+ try {
423
+ enDisco = existsSync(ruta) ? huella(readFileSync(ruta, "utf8")) : undefined;
424
+ }
425
+ catch {
426
+ // Un `.md` que no se puede leer no se puede comparar, y tampoco se pisa: el cargador
427
+ // ya dirá por qué no se pudo leer.
428
+ continue;
429
+ }
430
+ if (adoptando) {
431
+ // La ronda de adopción: se anota lo que hay y no se escribe nada.
432
+ if (enDisco === undefined)
433
+ marca[agente.nombre] = nuestro;
434
+ else if (enDisco === nuestro)
435
+ marca[agente.nombre] = nuestro;
436
+ else {
437
+ marca[agente.nombre] = AJENO;
438
+ desactualizados.push(agente.nombre);
439
+ }
440
+ continue;
441
+ }
442
+ if (enDisco === undefined) {
443
+ // Sin fichero: nuevo si no consta que lo hubiéramos entregado; borrado si consta.
444
+ if (marca[agente.nombre] === undefined) {
445
+ writeFileSync(ruta, contenido, "utf8");
446
+ marca[agente.nombre] = nuestro;
447
+ escritos.push(agente.nombre);
448
+ }
449
+ continue;
450
+ }
451
+ if (enDisco === nuestro) {
452
+ marca[agente.nombre] = nuestro;
453
+ continue;
454
+ }
455
+ if (marca[agente.nombre] !== undefined && marca[agente.nombre] !== AJENO && marca[agente.nombre] === enDisco) {
456
+ // Es exactamente lo que escribimos la última vez y la versión de serie ha cambiado:
457
+ // nadie lo ha tocado, así que se actualiza.
458
+ writeFileSync(ruta, contenido, "utf8");
459
+ marca[agente.nombre] = nuestro;
460
+ escritos.push(agente.nombre);
461
+ continue;
462
+ }
463
+ marca[agente.nombre] = AJENO;
464
+ desactualizados.push(agente.nombre);
465
+ }
466
+ /**
467
+ * El quinto caso: una clave de la marca que ya no nombra a ningún agente de serie.
468
+ *
469
+ * Solo la deja un renombrado, y solo se mira la de los nombres que constan en `RENOMBRADOS`.
470
+ * El desenlace se decide con el mismo dato que todo lo demás: si el fichero sigue siendo
471
+ * exactamente lo que escribimos, es nuestra semilla y se retira; si no, es del usuario y se
472
+ * queda. Sin marca no se puede saber —esa es la carpeta que se adopta, y ahí el huérfano se
473
+ * queda y no se dice: es el límite, y es el lado que no borra nada ajeno.
474
+ */
475
+ const retirados = [];
476
+ for (const [viejo, ahoraSeLlama] of Object.entries(RENOMBRADOS)) {
477
+ const anotado = marca[viejo];
478
+ if (anotado === undefined)
479
+ continue;
480
+ const ruta = join(carpeta, `${viejo}.md`);
481
+ let enDisco;
482
+ try {
483
+ enDisco = existsSync(ruta) ? huella(readFileSync(ruta, "utf8")) : undefined;
484
+ }
485
+ catch {
486
+ // Sin comparación no hay borrado: un `.md` ilegible se queda, y el cargador dirá por qué.
487
+ continue;
488
+ }
489
+ if (enDisco === undefined) {
490
+ // El fichero ya no está: la clave se va con él, y no hay nada que contarle a nadie.
491
+ delete marca[viejo];
492
+ continue;
493
+ }
494
+ if (enDisco === anotado) {
495
+ rmSync(ruta);
496
+ delete marca[viejo];
497
+ retirados.push({ nombre: viejo, ahoraSeLlama, borrado: true });
498
+ continue;
499
+ }
500
+ // Afinado por el usuario: se queda, y se DICE cada arranque mientras siga ahí — queda algo
501
+ // que decidir, que es borrarlo o quedarse con los dos.
502
+ marca[viejo] = AJENO;
503
+ retirados.push({ nombre: viejo, ahoraSeLlama, borrado: false });
504
+ }
505
+ escribirSemilla(carpeta, marca);
506
+ return { escritos, desactualizados, retirados };
507
+ }
508
+ /**
509
+ * El bloque de skills visuales, que los cuatro comparten.
510
+ *
511
+ * Va en el CUERPO de los sembrados y no en `REGLAS_XONE`: no es una regla del dominio, es
512
+ * cómo usar dos skills concretas. Un agente que el usuario escriba sin `archify` ni
513
+ * `artifacts-builder` no tiene por qué leer instrucciones sobre tools que no tiene —
514
+ * hablarle de una capacidad que no posee es la misma clase de mentira que un botón muerto.
515
+ */
516
+ /**
517
+ * Las dos skills visuales, dónde se GUARDA lo que dibujan y qué NO funciona donde se VE.
518
+ *
519
+ * La última regla —los almacenes del navegador— está aquí y no en la skill por una razón
520
+ * medida: `artifacts-builder/SKILL.md` la explica con detalle y **el modelo no lo abre**. En
521
+ * las tres delegaciones medidas leyó `archify/SKILL.md` y `reference/diagramas.md`, y ni una
522
+ * vez el `SKILL.md` ni `reference/estilo.md`. El resultado, en vivo el 2026-09-07: un
523
+ * artefacto recién escrito puso un interruptor de tema con `localStorage.getItem` en la
524
+ * última línea de su arranque, lanzó `SecurityError` dentro del iframe y se llevó consigo el
525
+ * `setAttribute` del tema — página perfecta y botón muerto. Es la misma lección que la
526
+ * carpeta `/artefactos/`: lo que tiene que cumplirse va donde el modelo mira SIEMPRE, no en
527
+ * una skill que hay que cargar.
528
+ */
529
+ /**
530
+ * **Ya no existe, y su hueco se deja escrito a propósito.**
531
+ *
532
+ * Era un bloque de ~1.400 caracteres —cómo elegir entre `archify` y `artifacts-builder`, dónde
533
+ * se guarda un artefacto y qué no funciona dentro del iframe— que iba en el prompt de CUATRO
534
+ * especialistas y por tanto viajaba en cada una de sus llamadas, hablaran o no de diagramas. Se
535
+ * midió lo que costaba con `inspectorDePrompt`: en un turno normal, la cabecera es el 87 % de
536
+ * la petición.
537
+ *
538
+ * Nació de una lección buena: un turno leyó `archify/SKILL.md` y una referencia, se saltó
539
+ * `estilo.md` —donde estaba la regla— y escribió un artefacto con `localStorage` que se mató
540
+ * solo. Pero la conclusión correcta de eso no era «repítelo en todos los prompts», era
541
+ * **ponerlo en el fichero que sí se abre**: hoy la regla encabeza el cuerpo de
542
+ * `skills/archify/SKILL.md` y `skills/artifacts-builder/SKILL.md`.
543
+ *
544
+ * Y hay una segunda razón para no nombrar las skills aquí: `SkillsMiddleware` ya las anuncia
545
+ * con su descripción y su ruta, y nombrarlas otra vez —con instrucciones de uso— es lo que
546
+ * llevaba al especialista a cargarlas antes de saber si le hacían falta.
547
+ */
548
+ /**
549
+ * La memoria del proyecto, y quién la lee y la escribe.
550
+ *
551
+ * También vivía en `promptDe`, repartida en dos ternarios sobre el nombre del perfil. Va al
552
+ * cuerpo del fichero y no a `REGLAS_XONE` porque es una POLÍTICA de estos cuatro, no una
553
+ * regla del dominio: un agente que el usuario escriba puede querer no leerla, y `docs`
554
+ * —que contesta de la plataforma y no del proyecto— tampoco la lee.
555
+ */
556
+ /**
557
+ * Trabajar CON un plan, cuando lo hay.
558
+ *
559
+ * El plan no es un documento que alguien entrega y se archiva: es el estado compartido entre
560
+ * quien planifica y quien desarrolla, y por eso vive en `/planes/<nombre>/` —fuera del proyecto,
561
+ * fuera de git y fuera de CloudStudio— y sobrevive a la sesión. Sin esta instrucción el plan se
562
+ * escribía y nadie volvía a mirarlo: medido, los ocho proyectos del usuario tenían CERO.
563
+ *
564
+ * **Se marca lo COMPROBADO, no lo escrito**, que es la misma regla que gobierna todo lo demás
565
+ * aquí: un comando que devuelve 0 no dice que la app arrancara, y un fichero guardado no dice
566
+ * que el criterio se cumpla.
567
+ */
568
+ const TRABAJAR_CON_PLAN = [
569
+ "SI TU ENCARGO NOMBRA UN PLAN (`/planes/<nombre>/`):",
570
+ "- Lee su `TASKS.md` ANTES de tocar nada y trabaja la tarea que te toque, no el plan entero.",
571
+ " Su `PLAN.md` y su `CONTEXT.md` están al lado si te falta contexto; no los redescubras.",
572
+ "- Y ACTUALÍZALO en el mismo sitio cuando termines: `**Estado:**` a `hecha`, y las casillas",
573
+ " de los criterios a `- [x]` solo si los has COMPROBADO — no por haber escrito el código que",
574
+ " debería cumplirlos. Lo que no puedas comprobar se queda sin marcar y lo dices.",
575
+ "- Si el plan se contradice con lo que ves en el código, no lo sigas a ciegas: dilo y para.",
576
+ ].join("\n");
577
+ const MEMORIA_LEER = [
578
+ "Para una tarea sobre este proyecto, lee una sola vez `/MEMORIA_PROYECTO.md` antes de inspeccionarlo.",
579
+ "No la uses para preguntas generales de plataforma.",
580
+ ].join(" ");
581
+ const MEMORIA_LEER_CON_HANDOFF = "Lee `/MEMORIA_PROYECTO.md` solo si la tarea NO incluye un `HANDOFF DE ANÁLISIS`. Con handoff, no la leas: sus hechos pertinentes ya vienen resumidos.";
582
+ const MEMORIA_ESCRIBIR = [
583
+ "Al terminar trabajo relevante, actualiza esa memoria solo con hechos comprobados, decisiones aprobadas",
584
+ "o pendientes útiles. Nunca copies transcripciones, salidas de tools, secretos ni ficheros completos.",
585
+ ].join(" ");
586
+ const RECONOCIMIENTO_PLANNER = [
587
+ "RECONOCIMIENTO RÁPIDO DEL PROYECTO:",
588
+ "- Para preguntas generales como «qué hace esta app», busca evidencia suficiente, no un inventario completo.",
589
+ "- Lee `/app.xml` y, como máximo, tres ficheros representativos que ese contexto señale.",
590
+ "- En cada primera lectura usa exactamente `offset=0` y `limit=50`; usa otra página solo si una evidencia concreta lo exige.",
591
+ "- No repitas una lectura de la misma ruta y rango, ni hagas búsquedas genéricas como `function ` sin una hipótesis.",
592
+ "- Cuando puedas identificar el propósito y los módulos principales con evidencia, deja de llamar tools y responde.",
593
+ "- Solo amplía la exploración si el usuario pide detalle exhaustivo o si las evidencias son insuficientes o contradictorias; explica brevemente qué faltaba.",
594
+ "- Si el resultado alimenta un diagrama o artefacto, termina con un `HANDOFF DE ANÁLISIS` compacto:",
595
+ " propósito; nodos; aristas `origen → destino`; evidencia `ruta:líneas`; y lagunas. No incluyas transcript ni lecturas crudas.",
596
+ ].join("\n");
597
+ /**
598
+ * La consulta acotada de `docs`, medida antes de escribirla.
599
+ *
600
+ * Trazado (`docs/EVALS.md`, «El coste, medido»): para decir que un atributo NO existe,
601
+ * `docs` hizo 26 llamadas y 317k tokens de entrada — tres `ls` y tres `glob` para
602
+ * inventariar `/skills`, ocho `grep` con `max_count: 30` en modo contenido (uno solo metió
603
+ * 1,4k tokens), y seis referencias leídas por páginas. Y nunca abrió
604
+ * `references/indice-completo.md`, que es el índice de las 55 referencias y la primera
605
+ * fila de `SKILL.md`. La regla no le quita capacidad: le dice dónde está el índice y
606
+ * cuándo la respuesta ya es «no está documentado» — que en XOne significa «no existe, no
607
+ * lo uses», y seguir buscando sinónimos no lo hace existir.
608
+ */
609
+ const CONSULTA_ACOTADA_DOCS = [
610
+ "CONSULTA ACOTADA DE LAS REFERENCIAS:",
611
+ "- `/skills/<skill>/SKILL.md` es la regla corta y `references/indice-completo.md` es el índice de TODAS las",
612
+ " referencias: léelo antes de buscar. No hagas `ls` ni `glob` sobre `/skills`; el índice ya dice qué hay y dónde.",
613
+ "- Abre como máximo tres referencias por pregunta, las que el índice señale para el tema, con `offset=0` y",
614
+ " `limit=100`; pide otra página solo si el índice o el propio fichero dicen que lo buscado sigue ahí.",
615
+ "- Un `grep` por hipótesis concreta (un atributo, una función, una clase), con `max_count=5` y sobre la carpeta",
616
+ " de su familia. Nunca dos sinónimos seguidos de lo mismo, ni una búsqueda sin hipótesis, ni repetir una",
617
+ " lectura de la misma ruta y rango.",
618
+ "- Si el índice y las referencias del tema no nombran lo que se pregunta, la respuesta es que NO está",
619
+ " documentado y por tanto no existe para XOne: dilo, nombra las referencias que miraste, y para.",
620
+ "- En cuanto tengas la evidencia para contestar, deja de llamar tools y responde.",
621
+ ].join("\n");
622
+ const HANDOFF_MOCKUP = [
623
+ "HANDOFF PARA DIAGRAMAS:",
624
+ "- Si la descripción de tu tarea incluye `HANDOFF DE ANÁLISIS`, ese bloque es tu evidencia de código real.",
625
+ "- Úsalo como fuente para el diagrama y NO vuelvas a leer, buscar ni reconstruir las rutas ya documentadas.",
626
+ "- Solo inspecciona un fichero si el handoff marca una laguna o dos evidencias se contradicen; explica cuál es la laguna.",
627
+ ].join("\n");
628
+ /**
629
+ * El que CONDUCE el aparato. Su conocimiento del protocolo NO va aquí: va en la skill
630
+ * `xone-hotswap`, que son 1.100 líneas de referencia sobre las dos plataformas y se cargan
631
+ * solo cuando hacen falta. Y las reglas de método —por nombre y no por coordenadas, esperar
632
+ * a un control y no a un temporizador, un `result:false` es el resultado— tampoco: están en
633
+ * esa misma skill, y aquí se pagarían en cada llamada, dibuje o no.
634
+ *
635
+ * **Lo que queda es lo que la skill no puede saber: cómo es ESTA casa.** Dónde están sus
636
+ * ficheros cuando se mira el disco de verdad, dónde se deja lo que una persona va a ver, y
637
+ * qué hacer con un comando que no termina. Nada de eso es de XOne ni de Android: es del
638
+ * harness, y por eso vive en código y no en una skill que mañana se sustituye por otra.
639
+ *
640
+ * Este bloque vale para CUALQUIER agente con `ejecucion: true`, no solo para éste. Está
641
+ * escrito así a propósito: el día que haya un segundo, lo hereda sin copiarse.
642
+ */
643
+ const EJECUCION_EN_LA_MAQUINA = [
644
+ "CONDUCES UN APARATO Y EJECUTAS COMANDOS EN ESTA MÁQUINA (`execute`).",
645
+ "",
646
+ "Los scripts de tus skills están EN EL PATH: se llaman por su nombre, sin ruta ninguna.",
647
+ "Los de `xone-hotswap` son `xone-arrancar-android` (levanta el emulador y ESPERA a que",
648
+ "esté listo; ya arrancado, lo dice y no hace nada), `xone-reiniciar-android` (déjalo en",
649
+ "estado conocido: es lo primero que hay que probar si el canal no contesta),",
650
+ "`xone-desplegar-android`,",
651
+ "`xone-hotswap`, `xone-log-android` (las excepciones del aparato, que es donde está el",
652
+ "porqué cuando algo no se pinta), `xone-captura-android` y `xone-arrancar-ios`;",
653
+ "cualquiera con `--help` o sin argumentos te dice cómo se usa. Úsalos: hacen lo que te piden",
654
+ "y no hay que reescribirlos.",
655
+ "",
656
+ "`read_file`, `ls`, `glob` y `grep` ven rutas VIRTUALES —la raíz `/` es el proyecto—, así que",
657
+ "una ruta absoluta del disco no existe para ellas y no va a funcionar por mucho que la",
658
+ "reintentes. Para mirar un fichero de una skill usa `cat`, que sí ve el disco de verdad.",
659
+ "",
660
+ "SI LA APP PIDE LOGIN Y NO SABES ENTRAR, no lo deduzcas leyendo el código: pregúntaselo al",
661
+ "APARATO, que tiene la base de datos de verdad —`runSql` con",
662
+ "`select LOGIN, PWD from <prefijo>_usuarios`, donde `<prefijo>` es el `prefix` del `<app>`",
663
+ "de `app.xml`—. Mira también si `autologon` está puesto. Y si aun así no entras, PÁRATE:",
664
+ "cuenta qué probaste y qué te falta. Quien lee tu respuesta puede darte las credenciales;",
665
+ "gastar el turno probando contraseñas no.",
666
+ "",
667
+ "PARA LLEGAR A UNA PANTALLA CONCRETA, no vayas a ciegas: `xone_navegacion` con",
668
+ "`operacion: \"referencias\"` y el nombre de la colección te dice QUÉ control lleva hasta ella",
669
+ "y en qué fichero está. Y si ese control no aparece en el árbol, no es que no exista: es que",
670
+ "todavía no se ve — mira el árbol, abre lo que haga falta (un cajón, un menú, la pantalla de",
671
+ "antes) y vuelve a mirar, en vez de repetir el mismo clic.",
672
+ "",
673
+ "CÓMO TRABAJAS, QUE ES LO QUE DECIDE SI ESTO SALE BARATO O CARO:",
674
+ "- UN comando, miras su salida, y entonces decides el siguiente. Nada de disparar diez a la",
675
+ " vez para ver cuál pega: la salida del primero cambia lo que hay que hacer después.",
676
+ "- Pero pide de una vez lo que sabes que vas a necesitar: lo caro no es lo que un comando",
677
+ " devuelve, es cada ida y vuelta, que reenvía toda la conversación. `xone-hotswap` acepta",
678
+ " varios comandos seguidos, y `xone-desplegar-android --captura` lanza Y captura en una.",
679
+ "- Si uno falla, cambia de hipótesis en vez de repetirlo. Dos intentos iguales dan lo mismo.",
680
+ "- No escribas ficheros sueltos en la raíz del proyecto: es la app del usuario, y ahí va a",
681
+ " aprobación, a git y a CloudStudio. Lo que quieras ENSEÑAR va a `$XONECODE_ARTEFACTOS`, y",
682
+ " los scripts ya lo hacen por ti. Una captura es `xone-hotswap '{\"command\":\"getScreenshot\"}'`",
683
+ " y, si el canal no contesta, `xone-captura-android` — nunca un `adb … screencap > fichero`,",
684
+ " que el `cwd` es la raíz del proyecto.",
685
+ "- EMPIEZA por mirar qué hay: `adb devices`. Sin ningún aparato, `xone-arrancar-android`",
686
+ " ANTES de nada más — no lo compongas a mano, que `emulator` no suele estar en el PATH.",
687
+ "- Un comando que no termina cuelga el turno: lo que no vuelve va al fondo (`… &`) y se",
688
+ " espera a una CONDICIÓN acotada, no a un número de segundos.",
689
+ "",
690
+ "LO QUE CUENTAS:",
691
+ "- La salida literal de lo que corriste, no tu interpretación. Un comando que devuelve 0 no",
692
+ " dice que la app arrancara.",
693
+ "- Y no digas que hiciste un paso que no hiciste: si capturaste lo que ya había en pantalla",
694
+ " sin desplegar el proyecto, eso es lo que hay que contar.",
695
+ "- Si no pudiste comprobar algo, dilo en vez de deducirlo.",
696
+ ].join("\n");
697
+ /**
698
+ * Los cinco. Nacieron como una mudanza de los textos que había en código; desde entonces
699
+ * `docs` lleva además la consulta acotada, y el que conduce aparatos llegó con la documentación
700
+ * del protocolo hotswap y pasó por dos nombres más antes de `device-controller` (ver
701
+ * `RENOMBRADOS`). Cada regla que
702
+ * se añade aquí se mide antes con los evals, porque un prompt más largo es coste en TODAS las
703
+ * llamadas.
704
+ */
705
+ export const AGENTES_DE_SERIE = [
706
+ {
707
+ nombre: "consultant-xone",
708
+ descripcion: "Para preguntas técnicas de la plataforma XOne: XML/.xne, JavaScript, CSS, eventos y " +
709
+ "patrones. Úsalo cuando la duda sea «¿cómo se hace esto en XOne?» o «¿este atributo " +
710
+ "existe?», no para averiguar qué hay en el proyecto. Dale la pregunta concreta y el " +
711
+ "contexto que ya hayas visto. Devuelve la respuesta anclada en la documentación de la " +
712
+ "plataforma, y dice cuándo no lo sabe en vez de deducirlo.",
713
+ motor: "modelo",
714
+ soloLectura: true,
715
+ /**
716
+ * **Sin las visuales, y eso es una DECISIÓN de reparto, no un recorte a ciegas.**
717
+ *
718
+ * Cada skill asignada mete en su prompt de sistema el nombre, la descripción y la ruta que
719
+ * pone `SkillsMiddleware`, y la descripción de `archify` sola son ~700 caracteres de inglés
720
+ * sobre diagramas, ETL y Mermaid — en CADA llamada, dibuje o no. Este contesta preguntas de
721
+ * la plataforma; la regla del orquestador ya manda los diagramas a `designer-xone`, así que
722
+ * dárselas era pagar una capacidad que no le toca ejercer.
723
+ */
724
+ skills: ["xone-development"],
725
+ instrucciones: `${CONSULTA_ACOTADA_DOCS}`,
726
+ origen: "semilla",
727
+ },
728
+ {
729
+ nombre: "analyst-xone",
730
+ descripcion: "Para enterarte de cómo es ESTE proyecto antes de tocarlo: qué colecciones hay, dónde " +
731
+ "se declara algo, quién usa qué. Úsalo como PRIMER paso de un encargo que vaya a " +
732
+ "cambiar código, y no para preguntas de la plataforma. Dale qué hay que averiguar y " +
733
+ "para qué, que es lo que acota cuánto busca. Devuelve hechos con su fichero y su " +
734
+ "línea, listos para pasárselos al siguiente en un bloque HANDOFF DE ANÁLISIS. Para un " +
735
+ "desarrollo grande puede además dejar un PLAN escrito en `/planes/<nombre>/` —spec, " +
736
+ "tareas y glosario— y entonces te dice el nombre: pásaselo a quien desarrolle.",
737
+ motor: "modelo",
738
+ soloLectura: true,
739
+ skills: ["xone-spec-builder", "xone-plan-builder", "archify", "artifacts-builder"],
740
+ instrucciones: `${RECONOCIMIENTO_PLANNER}\n\n${MEMORIA_LEER}`,
741
+ origen: "semilla",
742
+ },
743
+ {
744
+ nombre: "developer-xone",
745
+ descripcion: "Para CAMBIAR el proyecto: crear o modificar colecciones, escribir scripts, editar " +
746
+ "ficheros. Dale los hechos ya averiguados —no le hagas redescubrirlos— y QUÉ tiene que " +
747
+ "conseguir, no cómo. Si hay un plan en `/planes/<nombre>/`, dale el nombre y la tarea " +
748
+ "concreta: lo lee y marca ahí lo que deja hecho. Devuelve los ficheros que cambió; cada " +
749
+ "escritura para el turno y pide aprobación, así que un encargo enorme son muchas " +
750
+ "paradas: pártelo.",
751
+ motor: "modelo",
752
+ soloLectura: false,
753
+ // `artifacts-builder` se queda: escribe documentos e informes. `archify` no, que los
754
+ // diagramas son de `designer-xone` y su descripción son ~700 caracteres por llamada.
755
+ skills: ["xone-development", "xone-debugging", "artifacts-builder"],
756
+ instrucciones: `${TRABAJAR_CON_PLAN}\n\n${MEMORIA_LEER}\n\n${MEMORIA_ESCRIBIR}`,
757
+ origen: "semilla",
758
+ },
759
+ {
760
+ nombre: "device-controller",
761
+ descripcion: "Para PROBAR en un móvil o emulador de verdad: despliega, navega hasta una pantalla y " +
762
+ "mira qué pasa. Es el único que ve lo que ninguna comprobación estática puede ver. " +
763
+ "Dile SIEMPRE a qué pantalla o colección llegar y qué comprobar ahí: «pruébalo» a " +
764
+ "secas le hace improvisar y sale caro. Levanta el emulador si no hay ninguno. Devuelve " +
765
+ "lo que MIDIÓ —salida literal, captura y excepciones del log—, y dice lo que no pudo " +
766
+ "comprobar en vez de deducirlo. No lo uses para cambiar el proyecto: eso es de " +
767
+ "developer-xone. Y NO lo uses para algo que se ve LEYENDO —un texto cortado, un " +
768
+ "atributo, un error de sintaxis—: arrancar un aparato para eso se come un cuarto del " +
769
+ "turno y no contesta nada que no conteste abrir el fichero.",
770
+ motor: "modelo",
771
+ /**
772
+ * **NO es de solo lectura, y decirlo importa por dos cosas distintas.**
773
+ *
774
+ * La primera es honestidad: tiene una shell, así que puede escribir el disco entero. Una
775
+ * pastilla verde de «solo lectura» sobre el único agente que alcanza la máquina es
776
+ * exactamente el tipo de etiqueta que enseña a no mirarlas.
777
+ *
778
+ * La segunda es que ese campo DECIDE EL MODELO: `rapido` para quien solo lee, `trabajo`
779
+ * para quien escribe. Conducir un aparato —leer un log, entender una excepción de Rhino,
780
+ * decidir el siguiente comando— no es trabajo de modelo barato, y medido con uno rápido se
781
+ * quedaba en bucles: probaba lo mismo dos veces, mezclaba rutas virtuales con reales y
782
+ * tardaba tres turnos en hacer lo que hace en uno. `soloLectura` se refería a los FICHEROS
783
+ * y aquí se estaba leyendo como «tarea sencilla», que es otra cosa.
784
+ *
785
+ * Lo que NO cambia: sigue sin `write_file` ni `edit_file` (`TOOLS_CON_EJECUCION`), para que
786
+ * el camino normal de tocar el proyecto siga siendo el que pasa por la aprobación y el diff.
787
+ */
788
+ soloLectura: false,
789
+ /**
790
+ * **El único de serie con ejecución**, y lo que concede no es «correr un comando»: una
791
+ * shell no pasa por `permisosDe` ni por el `virtualMode`, así que alcanza el disco
792
+ * entero. Se le da porque es lo único que contesta «lanza la app»: el canal del aparato
793
+ * es un WebSocket —`curl` no lo habla— y el despliegue es `adb` más una subida HTTP.
794
+ * Ninguna de las dos cosas se compone leyendo, y un prompt no las suple.
795
+ */
796
+ ejecucion: true,
797
+ skills: ["xone-hotswap", "xone-debugging"],
798
+ instrucciones: EJECUCION_EN_LA_MAQUINA,
799
+ origen: "semilla",
800
+ },
801
+ {
802
+ nombre: "designer-xone",
803
+ descripcion: "Para cambiar cómo se VE algo: el layout de una pantalla, el CSS, los recursos, y los " +
804
+ "diagramas de la app. Suyo es TODO síntoma visual, y da igual que suene a avería: algo " +
805
+ "CORTADO, que SE SALE, que NO SE LEE, que NO CABE, apretado, pegado al borde, " +
806
+ "desalineado, del color o del tamaño equivocado — también cuando se arregle en un " +
807
+ "`.xne` y no en el CSS. De developer-xone es qué HACE un botón al pulsarlo, aunque se " +
808
+ "toque el CSS. Dale la pantalla, qué se ve mal y qué tendría que verse — y si el " +
809
+ "arreglo depende del código real, el análisis ya hecho. Devuelve los ficheros que " +
810
+ "cambió, con aprobación como cualquier otra escritura.",
811
+ motor: "modelo",
812
+ soloLectura: false,
813
+ skills: ["xone-development", "archify", "artifacts-builder"],
814
+ instrucciones: `${TRABAJAR_CON_PLAN}\n\n${HANDOFF_MOCKUP}\n\n${MEMORIA_LEER_CON_HANDOFF}\n\n${MEMORIA_ESCRIBIR}`,
815
+ origen: "semilla",
816
+ },
817
+ ];
818
+ //# sourceMappingURL=agentesEnDisco.js.map