xonecode 0.3.0 → 0.5.1

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 (408) hide show
  1. package/README.md +50 -8
  2. package/THIRD_PARTY_NOTICES.md +160 -0
  3. package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
  63. package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
  64. package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
  65. package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
  66. package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
  67. package/apps/web/dist/assets/html-CfGypltT.js +1 -0
  68. package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
  69. package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
  70. package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
  71. package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  72. package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  73. package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  74. package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  75. package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  76. package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  77. package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  78. package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
  79. package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  80. package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  81. package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  82. package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  83. package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  84. package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  85. package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
  86. package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
  87. package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  88. package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  89. package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
  90. package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
  91. package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
  92. package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
  93. package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
  94. package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
  95. package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
  96. package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
  97. package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  98. package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
  99. package/apps/web/dist/iconos/xonecode.png +0 -0
  100. package/apps/web/dist/index.html +20 -0
  101. package/dist/agent/agentesEnDisco.js +539 -0
  102. package/dist/agent/agentesEnDisco.js.map +1 -0
  103. package/dist/agent/arbolDeProyecto.js +245 -0
  104. package/dist/agent/arbolDeProyecto.js.map +1 -0
  105. package/dist/agent/artefactosEnDisco.js +139 -0
  106. package/dist/agent/artefactosEnDisco.js.map +1 -0
  107. package/dist/agent/aumentador.js +175 -0
  108. package/dist/agent/aumentador.js.map +1 -0
  109. package/dist/agent/authEnDisco.js +76 -1
  110. package/dist/agent/authEnDisco.js.map +1 -1
  111. package/dist/agent/busquedaRegex.js +92 -0
  112. package/dist/agent/busquedaRegex.js.map +1 -0
  113. package/dist/agent/catalogoModelos.js +345 -0
  114. package/dist/agent/catalogoModelos.js.map +1 -0
  115. package/dist/agent/checkpointer.js +127 -0
  116. package/dist/agent/checkpointer.js.map +1 -0
  117. package/dist/agent/cloudstudioClient.js +194 -0
  118. package/dist/agent/cloudstudioClient.js.map +1 -0
  119. package/dist/agent/cloudstudioMcp.js +722 -0
  120. package/dist/agent/cloudstudioMcp.js.map +1 -0
  121. package/dist/agent/configEnDisco.js +309 -11
  122. package/dist/agent/configEnDisco.js.map +1 -1
  123. package/dist/agent/consumoExterno.js +99 -0
  124. package/dist/agent/consumoExterno.js.map +1 -0
  125. package/dist/agent/descarga.js +149 -0
  126. package/dist/agent/descarga.js.map +1 -0
  127. package/dist/agent/diagnosticoDeTools.js +51 -0
  128. package/dist/agent/diagnosticoDeTools.js.map +1 -0
  129. package/dist/agent/dispositivosEnMaquina.js +596 -0
  130. package/dist/agent/dispositivosEnMaquina.js.map +1 -0
  131. package/dist/agent/escrituraDeCodex.js +134 -0
  132. package/dist/agent/escrituraDeCodex.js.map +1 -0
  133. package/dist/agent/escrituraDeOpencode.js +80 -0
  134. package/dist/agent/escrituraDeOpencode.js.map +1 -0
  135. package/dist/agent/escrituraExterna.js +742 -0
  136. package/dist/agent/escrituraExterna.js.map +1 -0
  137. package/dist/agent/gemini.js +81 -0
  138. package/dist/agent/gemini.js.map +1 -0
  139. package/dist/agent/git.js +83 -0
  140. package/dist/agent/git.js.map +1 -0
  141. package/dist/agent/gitSync.js +441 -0
  142. package/dist/agent/gitSync.js.map +1 -0
  143. package/dist/agent/hotswap.js +448 -0
  144. package/dist/agent/hotswap.js.map +1 -0
  145. package/dist/agent/instalacionEnMaquina.js +252 -0
  146. package/dist/agent/instalacionEnMaquina.js.map +1 -0
  147. package/dist/agent/instantanea.js +7 -10
  148. package/dist/agent/instantanea.js.map +1 -1
  149. package/dist/agent/interrupts.js +11 -2
  150. package/dist/agent/interrupts.js.map +1 -1
  151. package/dist/agent/juezDeTarea.js +345 -0
  152. package/dist/agent/juezDeTarea.js.map +1 -0
  153. package/dist/agent/lanzamientoEnMaquina.js +377 -0
  154. package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
  155. package/dist/agent/manifiesto.js +59 -0
  156. package/dist/agent/manifiesto.js.map +1 -0
  157. package/dist/agent/memoriaDeProyecto.js +60 -0
  158. package/dist/agent/memoriaDeProyecto.js.map +1 -0
  159. package/dist/agent/modelos.js +105 -17
  160. package/dist/agent/modelos.js.map +1 -1
  161. package/dist/agent/modelosDeMotor.js +204 -0
  162. package/dist/agent/modelosDeMotor.js.map +1 -0
  163. package/dist/agent/paqueteDelProyecto.js +181 -0
  164. package/dist/agent/paqueteDelProyecto.js.map +1 -0
  165. package/dist/agent/perfiles.js +92 -31
  166. package/dist/agent/perfiles.js.map +1 -1
  167. package/dist/agent/persona.js +40 -0
  168. package/dist/agent/persona.js.map +1 -0
  169. package/dist/agent/procesosEnMaquina.js +204 -0
  170. package/dist/agent/procesosEnMaquina.js.map +1 -0
  171. package/dist/agent/proyecto.js +311 -13
  172. package/dist/agent/proyecto.js.map +1 -1
  173. package/dist/agent/puente.js +63 -6
  174. package/dist/agent/puente.js.map +1 -1
  175. package/dist/agent/resumenDeContexto.js +30 -0
  176. package/dist/agent/resumenDeContexto.js.map +1 -0
  177. package/dist/agent/resumenDeTool.js +64 -18
  178. package/dist/agent/resumenDeTool.js.map +1 -1
  179. package/dist/agent/sesionGit.js +481 -0
  180. package/dist/agent/sesionGit.js.map +1 -0
  181. package/dist/agent/settingsEnDisco.js +189 -0
  182. package/dist/agent/settingsEnDisco.js.map +1 -0
  183. package/dist/agent/subagenteCodex.js +368 -0
  184. package/dist/agent/subagenteCodex.js.map +1 -0
  185. package/dist/agent/subagenteExterno.js +423 -0
  186. package/dist/agent/subagenteExterno.js.map +1 -0
  187. package/dist/agent/subagenteOpencode.js +389 -0
  188. package/dist/agent/subagenteOpencode.js.map +1 -0
  189. package/dist/agent/subida.js +202 -0
  190. package/dist/agent/subida.js.map +1 -0
  191. package/dist/agent/tareasEnDisco.js +466 -0
  192. package/dist/agent/tareasEnDisco.js.map +1 -0
  193. package/dist/agent/turnoReal.js +663 -50
  194. package/dist/agent/turnoReal.js.map +1 -1
  195. package/dist/agent/versionEnDisco.js +48 -0
  196. package/dist/agent/versionEnDisco.js.map +1 -0
  197. package/dist/agent/xoneAgent.js +290 -49
  198. package/dist/agent/xoneAgent.js.map +1 -1
  199. package/dist/agent/zip.js +52 -0
  200. package/dist/agent/zip.js.map +1 -0
  201. package/dist/cli/acuseDeModelo.js +30 -0
  202. package/dist/cli/acuseDeModelo.js.map +1 -0
  203. package/dist/cli/aprobar.js +55 -2
  204. package/dist/cli/aprobar.js.map +1 -1
  205. package/dist/cli/config.js +9 -13
  206. package/dist/cli/config.js.map +1 -1
  207. package/dist/cli/consola.js +750 -19
  208. package/dist/cli/consola.js.map +1 -1
  209. package/dist/cli/main.js +740 -81
  210. package/dist/cli/main.js.map +1 -1
  211. package/dist/cli/markdown.js +188 -2
  212. package/dist/cli/markdown.js.map +1 -1
  213. package/dist/cli/run.js +32 -4
  214. package/dist/cli/run.js.map +1 -1
  215. package/dist/cli/stdio.js +46 -19
  216. package/dist/cli/stdio.js.map +1 -1
  217. package/dist/cli/tema.js +96 -8
  218. package/dist/cli/tema.js.map +1 -1
  219. package/dist/cli/tokens.js +29 -0
  220. package/dist/cli/tokens.js.map +1 -0
  221. package/dist/cli/tui/app.js +171 -0
  222. package/dist/cli/tui/app.js.map +1 -0
  223. package/dist/cli/tui/aprobarTui.js +80 -0
  224. package/dist/cli/tui/aprobarTui.js.map +1 -0
  225. package/dist/cli/tui/barra.js +37 -0
  226. package/dist/cli/tui/barra.js.map +1 -0
  227. package/dist/cli/tui/correrTui.js +478 -0
  228. package/dist/cli/tui/correrTui.js.map +1 -0
  229. package/dist/cli/tui/entrada.js +166 -0
  230. package/dist/cli/tui/entrada.js.map +1 -0
  231. package/dist/cli/tui/filas.js +24 -0
  232. package/dist/cli/tui/filas.js.map +1 -0
  233. package/dist/cli/tui/logo.js +49 -0
  234. package/dist/cli/tui/logo.js.map +1 -0
  235. package/dist/cli/tui/pielTui.js +14 -0
  236. package/dist/cli/tui/pielTui.js.map +1 -0
  237. package/dist/cli/tui/raton.js +126 -0
  238. package/dist/cli/tui/raton.js.map +1 -0
  239. package/dist/cli/tui/sidebar.js +86 -0
  240. package/dist/cli/tui/sidebar.js.map +1 -0
  241. package/dist/cli/tui/store.js +143 -0
  242. package/dist/cli/tui/store.js.map +1 -0
  243. package/dist/cli/tui/tarjeta.js +19 -0
  244. package/dist/cli/tui/tarjeta.js.map +1 -0
  245. package/dist/cli/tui/temaInk.js +114 -0
  246. package/dist/cli/tui/temaInk.js.map +1 -0
  247. package/dist/cli/tui/transcript.js +254 -0
  248. package/dist/cli/tui/transcript.js.map +1 -0
  249. package/dist/cli/wizardInicial.js +240 -0
  250. package/dist/cli/wizardInicial.js.map +1 -0
  251. package/dist/core/actos.js +113 -0
  252. package/dist/core/actos.js.map +1 -0
  253. package/dist/core/adjuntos.js +97 -0
  254. package/dist/core/adjuntos.js.map +1 -0
  255. package/dist/core/agentes.js +227 -0
  256. package/dist/core/agentes.js.map +1 -0
  257. package/dist/core/artefactos.js +149 -0
  258. package/dist/core/artefactos.js.map +1 -0
  259. package/dist/core/cloudstudio.js +7 -0
  260. package/dist/core/cloudstudio.js.map +1 -0
  261. package/dist/core/config.js +212 -5
  262. package/dist/core/config.js.map +1 -1
  263. package/dist/core/contextos.js +53 -1
  264. package/dist/core/contextos.js.map +1 -1
  265. package/dist/core/descargas.js +106 -0
  266. package/dist/core/descargas.js.map +1 -0
  267. package/dist/core/descriptoresDeApp.js +279 -0
  268. package/dist/core/descriptoresDeApp.js.map +1 -0
  269. package/dist/core/dispositivos.js +466 -0
  270. package/dist/core/dispositivos.js.map +1 -0
  271. package/dist/core/entrega.js +141 -0
  272. package/dist/core/entrega.js.map +1 -0
  273. package/dist/core/entrelazar.js +90 -0
  274. package/dist/core/entrelazar.js.map +1 -0
  275. package/dist/core/esqueleto.js +10 -0
  276. package/dist/core/esqueleto.js.map +1 -1
  277. package/dist/core/modelos.js +235 -3
  278. package/dist/core/modelos.js.map +1 -1
  279. package/dist/core/notify.js +27 -9
  280. package/dist/core/notify.js.map +1 -1
  281. package/dist/core/planDeSubida.js +127 -0
  282. package/dist/core/planDeSubida.js.map +1 -0
  283. package/dist/core/ports.js +189 -1
  284. package/dist/core/ports.js.map +1 -1
  285. package/dist/core/puedeLanzarse.js +239 -0
  286. package/dist/core/puedeLanzarse.js.map +1 -0
  287. package/dist/core/rutaVirtual.js +42 -0
  288. package/dist/core/rutaVirtual.js.map +1 -0
  289. package/dist/core/settings.js +246 -0
  290. package/dist/core/settings.js.map +1 -0
  291. package/dist/core/tareas.js +230 -0
  292. package/dist/core/tareas.js.map +1 -0
  293. package/dist/core/textos.js +36 -0
  294. package/dist/core/textos.js.map +1 -0
  295. package/dist/core/turno.js +56 -8
  296. package/dist/core/turno.js.map +1 -1
  297. package/dist/core/version.js +31 -0
  298. package/dist/core/version.js.map +1 -0
  299. package/dist/vendor/sqliteSaver.js +389 -0
  300. package/dist/vendor/sqliteSaver.js.map +1 -0
  301. package/dist/vendor/tokenTracking.js +22 -1
  302. package/dist/vendor/tokenTracking.js.map +1 -1
  303. package/dist/web/servidor/arranque.js +3964 -0
  304. package/dist/web/servidor/arranque.js.map +1 -0
  305. package/dist/web/servidor/consolaDeTarea.js +161 -0
  306. package/dist/web/servidor/consolaDeTarea.js.map +1 -0
  307. package/dist/web/servidor/consolaWeb.js +389 -0
  308. package/dist/web/servidor/consolaWeb.js.map +1 -0
  309. package/dist/web/servidor/corredorDeTareas.js +1119 -0
  310. package/dist/web/servidor/corredorDeTareas.js.map +1 -0
  311. package/dist/web/servidor/pielWeb.js +269 -0
  312. package/dist/web/servidor/pielWeb.js.map +1 -0
  313. package/dist/web/servidor/servidor.js +257 -0
  314. package/dist/web/servidor/servidor.js.map +1 -0
  315. package/dist/web/servidor/sesiones.js +440 -0
  316. package/dist/web/servidor/sesiones.js.map +1 -0
  317. package/dist/web/servidor/transporte.js +169 -0
  318. package/dist/web/servidor/transporte.js.map +1 -0
  319. package/dist/web/servidor/vestibulo.js +1276 -0
  320. package/dist/web/servidor/vestibulo.js.map +1 -0
  321. package/package.json +33 -9
  322. package/skills/archify/LICENSE +22 -0
  323. package/skills/archify/SKILL.md +109 -0
  324. package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
  325. package/skills/archify/assets/template.html +14787 -0
  326. package/skills/archify/bin/archify.mjs +1990 -0
  327. package/skills/archify/bin/open-artifact.mjs +86 -0
  328. package/skills/archify/bin/preview.mjs +648 -0
  329. package/skills/archify/bin/visual-check.mjs +829 -0
  330. package/skills/archify/brand-marks/README.md +31 -0
  331. package/skills/archify/brand-marks/catalog.json +131 -0
  332. package/skills/archify/delta/architecture-delta.mjs +1214 -0
  333. package/skills/archify/examples/agent-run.lifecycle.json +71 -0
  334. package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
  335. package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
  336. package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
  337. package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
  338. package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
  339. package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
  340. package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
  341. package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
  342. package/skills/archify/examples/event-stream.dataflow.json +57 -0
  343. package/skills/archify/examples/incident-response.workflow.json +64 -0
  344. package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
  345. package/skills/archify/examples/product-analytics.dataflow.json +76 -0
  346. package/skills/archify/examples/production-deployment.architecture.json +71 -0
  347. package/skills/archify/examples/release-delivery.workflow.json +62 -0
  348. package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
  349. package/skills/archify/examples/web-app-rendered.html +14862 -0
  350. package/skills/archify/examples/web-app.architecture.json +46 -0
  351. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
  352. package/skills/archify/migrations/workflow-v2.mjs +279 -0
  353. package/skills/archify/package.json +17 -0
  354. package/skills/archify/recipes/scenarios.mjs +391 -0
  355. package/skills/archify/references/authoring-contract.md +198 -0
  356. package/skills/archify/references/brand-marks.md +69 -0
  357. package/skills/archify/references/delivery-contract.md +116 -0
  358. package/skills/archify/references/viewer-runtime.md +45 -0
  359. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  360. package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
  361. package/skills/archify/renderers/dataflow/README.md +104 -0
  362. package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  363. package/skills/archify/renderers/lifecycle/README.md +115 -0
  364. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  365. package/skills/archify/renderers/sequence/README.md +114 -0
  366. package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
  367. package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
  368. package/skills/archify/renderers/shared/cli.mjs +218 -0
  369. package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
  370. package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
  371. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  372. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  373. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  374. package/skills/archify/renderers/shared/geometry.mjs +1423 -0
  375. package/skills/archify/renderers/shared/i18n.mjs +594 -0
  376. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  377. package/skills/archify/renderers/shared/legend.mjs +217 -0
  378. package/skills/archify/renderers/shared/output-path.mjs +321 -0
  379. package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
  380. package/skills/archify/renderers/shared/text-fit.mjs +49 -0
  381. package/skills/archify/renderers/shared/utils.mjs +232 -0
  382. package/skills/archify/renderers/shared/validator.mjs +86 -0
  383. package/skills/archify/renderers/workflow/README.md +223 -0
  384. package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
  385. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  386. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  387. package/skills/archify/schemas/README.md +211 -0
  388. package/skills/archify/schemas/architecture.schema.json +176 -0
  389. package/skills/archify/schemas/common.schema.json +115 -0
  390. package/skills/archify/schemas/dataflow.schema.json +243 -0
  391. package/skills/archify/schemas/lifecycle.schema.json +266 -0
  392. package/skills/archify/schemas/sequence.schema.json +223 -0
  393. package/skills/archify/schemas/workflow.schema.json +428 -0
  394. package/skills/archify/scripts/check-render-output.mjs +835 -0
  395. package/skills/archify/scripts/check-update.mjs +1667 -0
  396. package/skills/archify/scripts/render-examples.mjs +26 -0
  397. package/skills/archify/scripts/update-contract.mjs +182 -0
  398. package/skills/archify/skill-release.json +10 -0
  399. package/skills/artifacts-builder/SKILL.md +120 -0
  400. package/skills/artifacts-builder/reference/diagramas.md +123 -0
  401. package/skills/artifacts-builder/reference/estilo.md +123 -0
  402. package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
  403. package/skills/artifacts-builder/reference/graficas.md +22 -0
  404. package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
  405. package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
  406. package/skills/xone-hotswap/SKILL.md +185 -0
  407. package/skills/xone-hotswap/references/comandos.md +706 -0
  408. package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
@@ -1,15 +1,33 @@
1
- import { readdirSync, statSync, existsSync, readFileSync } from "node:fs";
2
- import { join, sep, resolve } from "node:path";
1
+ import { readdirSync, lstatSync, statSync, existsSync, readFileSync, realpathSync } from "node:fs";
2
+ import { join, sep } from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
- import { HumanMessage } from "@langchain/core/messages";
4
+ import { HumanMessage, ToolMessage } from "@langchain/core/messages";
5
5
  import { Command, MemorySaver } from "@langchain/langgraph";
6
6
  import { collectPending, MAX_APPROVAL_ROUNDS } from "../vendor/hitl.js";
7
+ import { esRutaDeArtefacto } from "../core/artefactos.js";
8
+ /**
9
+ * Cuántas veces se le devuelven los hallazgos al agente para que los corrija. Dos: tres
10
+ * verificaciones en total. Un modelo que no arregla algo en dos intentos con el error
11
+ * delante no lo arregla al quinto, y cada intento son escrituras que pasan por aprobación
12
+ * humana — o sea, tiempo de una persona. La guarda de «no progresa» (misma huella dos
13
+ * veces) corta antes de llegar aquí cuando el modelo repite el mismo cambio.
14
+ */
15
+ export const TOPE_REPARACIONES = 2;
7
16
  import { aPendiente, ficheroDe, cambioDe, buildResume } from "./interrupts.js";
17
+ import { cargarAgentes } from "./agentesEnDisco.js";
18
+ import { crearSubagenteExterno } from "./subagenteExterno.js";
19
+ import { opcionesDeSubagenteExterno } from "./escrituraExterna.js";
20
+ import { ColaDeEventos, entrelazar } from "../core/entrelazar.js";
21
+ import { sumarConsumo, SIN_CONSUMO } from "./consumoExterno.js";
22
+ import { rutaRealDeVirtual } from "../core/rutaVirtual.js";
8
23
  import { correrTurno } from "../core/turno.js";
24
+ import { relative, resolve as resolverRuta } from "node:path";
9
25
  import { tomarInstantanea } from "./instantanea.js";
10
26
  import { construirAgente } from "./xoneAgent.js";
27
+ import { asegurarMemoriaDeProyecto } from "./memoriaDeProyecto.js";
11
28
  import { aEventos } from "./puente.js";
12
29
  import { createTokenTracker } from "../vendor/tokenTracking.js";
30
+ import { crearDiagnosticoDeTools } from "./diagnosticoDeTools.js";
13
31
  /**
14
32
  * Recorre la raíz y devuelve las rutas en el ESPACIO VIRTUAL del backend.
15
33
  *
@@ -17,8 +35,10 @@ import { createTokenTracker } from "../vendor/tokenTracking.js";
17
35
  * importar de agent; al revés no). El recorrido se hace EN CADA llamada a propósito:
18
36
  * la lista no se cachea porque los ficheros cambian durante la sesión.
19
37
  */
20
- export function ficherosDelProyecto(raiz, prof = 0) {
21
- if (prof > 4 || !existsSync(raiz))
38
+ /** Hasta dónde baja el completado del Tab. El árbol de la consola web pide más (`arbolDeProyecto.ts`). */
39
+ export const PROFUNDIDAD_DEL_TAB = 4;
40
+ export function ficherosDelProyecto(raiz, prof = 0, tope = PROFUNDIDAD_DEL_TAB) {
41
+ if (prof > tope || !existsSync(raiz))
22
42
  return new Set();
23
43
  const salida = new Set();
24
44
  for (const entrada of readdirSync(raiz)) {
@@ -26,12 +46,28 @@ export function ficherosDelProyecto(raiz, prof = 0) {
26
46
  continue;
27
47
  const ruta = join(raiz, entrada);
28
48
  try {
29
- if (statSync(ruta).isDirectory()) {
49
+ // `lstatSync` y no `statSync`: una CARPETA detrás de un enlace simbólico no se
50
+ // recorre. Medido en la pestaña Ficheros de la consola web: «carpeta-enlazada» →
51
+ // «.xonecode» listaba la carpeta denegada bajo otro nombre, y «dir-fuera» →
52
+ // «/algo/de/fuera» listaba nombres de ficheros que no son del proyecto (un
53
+ // «dir-fuera/id_rsa»). Tampoco se lista el enlace en sí: la carpeta a la que apunta
54
+ // o ya está en el árbol por su nombre real, o está fuera del proyecto y no debe
55
+ // salir — y una hoja que al abrirse dice «no se enseña» es un botón muerto.
56
+ // Un enlace a FICHERO sí se queda como hoja: quién decide si se puede leer es la
57
+ // barrera del lector (`arbolDeProyecto.ts`), que recomprueba sobre el `realpath`.
58
+ // Esta función alimenta a TRES consumidores y el cambio es correcto para los tres:
59
+ // el completado de «@ficheros» del Tab (no completa a carpetas ajenas), el universo
60
+ // que consulta `esVistaAplanada` (una vista aplanada de fuera del proyecto no es
61
+ // asunto nuestro) y el árbol de la consola web.
62
+ const info = lstatSync(ruta);
63
+ if (info.isSymbolicLink() && statSync(ruta).isDirectory())
64
+ continue;
65
+ if (info.isDirectory()) {
30
66
  // La recursión devuelve rutas relativas al SUBDIRECTORIO: sin recoserle el
31
67
  // nombre, «app/Clientes.xne» saldría como «/Clientes.xne» y el Set dejaría de
32
68
  // responder por las vistas aplanadas ANIDADAS (este universo es el que consulta
33
69
  // `esVistaAplanada`).
34
- for (const f of ficherosDelProyecto(ruta, prof + 1))
70
+ for (const f of ficherosDelProyecto(ruta, prof + 1, tope))
35
71
  salida.add(`/${entrada}${f}`);
36
72
  }
37
73
  else {
@@ -44,6 +80,73 @@ export function ficherosDelProyecto(raiz, prof = 0) {
44
80
  }
45
81
  return salida;
46
82
  }
83
+ /**
84
+ * El nodo de tools del grafo de `createAgent`, comprobado en el paquete instalado
85
+ * (`langchain/dist/agents/nodes/ToolNode.js`: `TOOLS_NODE_NAME = "tools"`). Hace falta para
86
+ * decirle a `updateState` de qué nodo viene lo que se añade; si el nombre cambiara, el
87
+ * remedio de abajo cae al `updateState` sin nodo, que es peor pero no rompe nada.
88
+ */
89
+ const NODO_DE_TOOLS = "tools";
90
+ /**
91
+ * Cierra las aprobaciones que se quedaron sin contestar cuando murió el proceso anterior.
92
+ *
93
+ * Solo existe desde que el hilo se guarda en disco (`agent/checkpointer.ts`), y arregla un
94
+ * fallo MEDIDO —y medido dos veces, porque la primera medida estaba mal—. La forma del
95
+ * grafo importa: un grafo de un solo nodo donde `START` va al nodo interrumpido reejecuta
96
+ * ese nodo con el mensaje nuevo y vuelve a preguntar, que es inofensivo. El grafo del
97
+ * agente NO tiene esa forma: `START` va al MODELO, y el nodo parado es el de tools. Así que
98
+ * al reabrir una sesión que se cerró con una aprobación delante, lo que le llega al modelo
99
+ * es `human → ai(tool_calls) → human`: un `AIMessage` con llamadas a tool y ningún
100
+ * `ToolMessage` detrás. Gemini y OpenAI rechazan exactamente eso, así que el primer mensaje
101
+ * tras reabrir se iba en un 400 — y nada lo explicaba.
102
+ *
103
+ * El remedio es una respuesta SINTÉTICA por cada llamada colgada, diciendo la verdad: no se
104
+ * aplicó, porque la sesión se cerró antes de que nadie decidiera. Con eso el historial es
105
+ * válido y además honesto — el modelo se entera de que aquella escritura no llegó a pasar,
106
+ * en vez de dar por hecho que sí. Y es cierto: el `interrupt` pausa ANTES de escribir, o
107
+ * sea que el disco no se tocó.
108
+ *
109
+ * Se hace al ABRIR y no al empezar el turno porque es un arreglo del pasado, no del turno:
110
+ * dentro de una sesión viva las aprobaciones se resuelven por su camino normal. Un fallo
111
+ * aquí no puede impedir abrir la sesión, así que se traga — lo que se pierde entonces es
112
+ * este arreglo, no la conversación.
113
+ *
114
+ * Se exporta para poder probarla sin montar un grafo de verdad, igual que `decisionDeTool`.
115
+ */
116
+ export async function saldarAprobacionesHuerfanas(agente, hilo) {
117
+ const cfg = { configurable: { thread_id: hilo } };
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ const grafo = agente;
120
+ try {
121
+ const estado = await grafo.getState(cfg);
122
+ const mensajes = estado?.values?.messages ?? [];
123
+ if (mensajes.length === 0)
124
+ return;
125
+ const contestadas = new Set(mensajes.filter((m) => m.getType() === "tool").map((m) => m.tool_call_id));
126
+ const colgadas = mensajes
127
+ .filter((m) => m.getType() === "ai")
128
+ .flatMap((m) => m.tool_calls ?? [])
129
+ .filter((c) => c.id !== undefined && !contestadas.has(c.id));
130
+ if (colgadas.length === 0)
131
+ return;
132
+ const respuestas = colgadas.map((c) => new ToolMessage({
133
+ content: "No se aplicó: la sesión se cerró antes de que nadie decidiera sobre esta escritura, " +
134
+ "así que el fichero no se tocó. Si sigue haciendo falta, vuelve a proponerlo.",
135
+ tool_call_id: c.id,
136
+ name: c.name,
137
+ status: "error",
138
+ }));
139
+ try {
140
+ await grafo.updateState(cfg, { messages: respuestas }, NODO_DE_TOOLS);
141
+ }
142
+ catch {
143
+ await grafo.updateState(cfg, { messages: respuestas });
144
+ }
145
+ }
146
+ catch {
147
+ // Abrir la sesión manda sobre arreglarla.
148
+ }
149
+ }
47
150
  /**
48
151
  * Abre una sesión real: agente, checkpointer, hilo y tracker se construyen UNA vez aquí, y
49
152
  * viven en el cierre. Todo lo que sobreviva al primer turno tiene que salir de este cierre:
@@ -51,19 +154,120 @@ export function ficherosDelProyecto(raiz, prof = 0) {
51
154
  */
52
155
  export async function abrirSesionReal(opciones) {
53
156
  const { raiz, entorno } = opciones;
54
- const checkpointer = new MemorySaver();
157
+ // Persiste fuera del checkpointer (que es solo de la sesión), pero no sobrescribe nunca
158
+ // una memoria ya creada por el usuario o por otra sesión.
159
+ asegurarMemoriaDeProyecto(raiz);
160
+ /**
161
+ * El tope de rondas de ESTA sesión, resuelto una vez. Ver `OpcionesDeSesion.topeDeRondas`:
162
+ * gobierna los tres sitios que antes miraban `MAX_APPROVAL_ROUNDS` a pelo, y ninguno de
163
+ * los tres puede quedarse fuera — la predicción de cierre tiene que usar exactamente el
164
+ * mismo número que el corte, o un turno se queda sin `fin` o cierra dos veces.
165
+ */
166
+ const topeDeRondas = opciones.topeDeRondas ?? MAX_APPROVAL_ROUNDS;
167
+ /**
168
+ * Quién autoriza las escrituras de un agente EXTERNO en esta sesión. Se resuelve una vez
169
+ * porque `pedirAprobacion` no cambia dentro de una sesión; la composición vive extraída
170
+ * (`escrituraExterna.ts#politicaExternaDeSesion`) y con test propio.
171
+ */
172
+ /**
173
+ * El buzón por el que la actividad de un agente EXTERNO entra al flujo de eventos.
174
+ *
175
+ * Vive en el cierre de la SESIÓN y no del turno porque el puerto se construye con el
176
+ * agente, que también es de la sesión. No hay riesgo de mezclar turnos: un agente externo
177
+ * solo corre dentro de uno, y lo que quedara encolado al agotarse un flujo se dice antes
178
+ * de cerrarlo (`entrelazar`).
179
+ */
180
+ const eventosExternos = new ColaDeEventos();
181
+ /**
182
+ * Lo que lleva consumido ESTA sesión, en dos cuentas que no se mezclan.
183
+ *
184
+ * No se mezclan porque no son el mismo dinero: los del grafo van contra la clave de API
185
+ * del usuario y los del hijo externo contra su suscripción del producto. Sumar TOKENS es
186
+ * legítimo —un token es un token— y sumar el COSTE sería la cifra que miente, así que
187
+ * aquí solo se cuentan tokens y las dos cuentas viajan separadas para que quien las pinte
188
+ * pueda decir la verdad sin tener que elegir una.
189
+ */
190
+ let consumoExterno = SIN_CONSUMO;
191
+ const oyentesDeConsumo = [];
192
+ const avisarDeConsumo = () => {
193
+ for (const o of oyentesDeConsumo) {
194
+ // Envuelto: un oyente que reviente no puede llevarse por delante un turno. Es la
195
+ // misma razón que el `try` alrededor de `alCambiar` del corredor de tareas, que sin
196
+ // él paraba el corredor entero.
197
+ try {
198
+ o();
199
+ }
200
+ catch {
201
+ // Un contador que no se pinta no es motivo para tumbar nada.
202
+ }
203
+ }
204
+ };
205
+ const checkpointer = opciones.checkpointer ?? new MemorySaver();
55
206
  const tracker = createTokenTracker();
207
+ const diagnostico = crearDiagnosticoDeTools(raiz);
56
208
  let modelos = opciones.modelos;
57
- let hilo = `xonecode-${randomUUID()}`;
209
+ let hilo = opciones.hilo ?? `xonecode-${randomUUID()}`;
210
+ let cancelarEnCurso;
211
+ let cerrada = false;
212
+ /**
213
+ * Los artefactos escritos en la pasada en curso. Los apunta el backend (es el único que
214
+ * sabe que la escritura ocurrió) y los VACÍA quien los anuncia, para que un artefacto se
215
+ * diga una vez y no en cada pasada de una reparación.
216
+ */
217
+ const artefactosDeLaPasada = [];
218
+ const carpetaDeArtefactos = opciones.artefactos ?? join(raiz, ".xonecode", "artefactos");
58
219
  const construir = async () => construirAgente({
59
220
  raiz,
60
221
  ficheros: ficherosDelProyecto(raiz),
222
+ // Se releen en CADA construcción del agente y no una vez al abrir la sesión: el
223
+ // usuario puede tocar un `.md` —o guardarlo desde Ajustes— con la consola abierta, y
224
+ // una lista congelada al arrancar le haría creer que su cambio no se aplicó.
225
+ agentes: cargarAgentes(raiz).agentes,
226
+ // Cada llamada al modelo mueve el tracker, y hay que DECIRLO: sin esto el único aviso
227
+ // lo daba el consumo de un agente externo, así que una sesión normal subía sus tokens
228
+ // en silencio y el contador de la web no aparecía nunca.
229
+ alContarTokens: avisarDeConsumo,
230
+ /**
231
+ * El adaptador real. Su import del SDK es dinámico, así que traerlo aquí no carga
232
+ * nada hasta que un agente externo esté dado de alta Y disponible.
233
+ *
234
+ * **La política de escritura sale del MISMO `pedirAprobacion` del HITL del grafo**, y
235
+ * eso es la mitad del diseño: sus implementaciones ya son las dos que hacen falta —la
236
+ * interactiva de las tres pieles, que enseña el diff y espera con plazo naciendo
237
+ * rechazada, y la autónoma de una tarea de fondo, que concede porque la autorización
238
+ * fue crear la tarea, lo ANUNCIA con los nombres y lo apunta en `Tarea.autorizadas`—.
239
+ * Un segundo hueco de política habría sido un segundo sitio donde el fail-closed
240
+ * puede dejar de estarlo. Sin `pedirAprobacion` no hay política, y sin política el
241
+ * hijo no escribe.
242
+ */
243
+ subagenteExterno: crearSubagenteExterno(opcionesDeSubagenteExterno({
244
+ ...(opciones.pedirAprobacion === undefined
245
+ ? {}
246
+ : { pedirAprobacion: opciones.pedirAprobacion }),
247
+ // Una FUNCIÓN y no la lista: el hijo escribe durante el turno, así que una foto
248
+ // congelada al abrir la sesión no vería el `.xne` que se acaba de crear — y su
249
+ // `.xml` aplanado dejaría de reconocerse como tal.
250
+ ficherosDelProyecto: () => ficherosDelProyecto(raiz),
251
+ eventos: eventosExternos,
252
+ alConsumir: (c) => {
253
+ consumoExterno = sumarConsumo(consumoExterno, c);
254
+ avisarDeConsumo();
255
+ },
256
+ })),
61
257
  modelos,
62
258
  skills: opciones.skills,
63
259
  checkpointer: checkpointer,
64
260
  tracker,
261
+ diagnostico,
262
+ artefactos: {
263
+ carpeta: carpetaDeArtefactos,
264
+ alEscribir: (a) => artefactosDeLaPasada.push(a),
265
+ },
266
+ // Ausente es «no hay», no una carpeta vacía: ver `OpcionesDeSesion.adjuntos`.
267
+ ...(opciones.adjuntos === undefined ? {} : { adjuntos: opciones.adjuntos }),
65
268
  });
66
269
  let agente = await construir();
270
+ await saldarAprobacionesHuerfanas(agente, hilo);
67
271
  /**
68
272
  * Los pendientes de aprobación, del ESTADO y no del resultado del stream.
69
273
  *
@@ -72,6 +276,29 @@ export async function abrirSesionReal(opciones) {
72
276
  * —`write_file` lleva el contenido entero—, pero aprobar a ciegas es peor que no aprobar,
73
277
  * así que la ruta sí. Una sola llamada a `getState` para las dos cosas.
74
278
  */
279
+ /**
280
+ * El ANTES del diff de una aprobación, leído del disco con las dos barreras: la lexical de
281
+ * `rutaRealDeVirtual` y la recomprobación sobre el camino REAL. Un fichero que no existe
282
+ * —el caso normal de un fichero nuevo— y uno cuyo destino se sale del proyecto dan lo
283
+ * mismo: cadena vacía, que es el lado conservador. No se distinguen a propósito: por aquí
284
+ * no se informa de nada, y decir «ese enlace apunta fuera» exigiría un canal que este
285
+ * lector no tiene.
286
+ */
287
+ const leerElAntes = (ruta) => {
288
+ const real = rutaRealDeVirtual(raiz, ruta);
289
+ if (real === undefined)
290
+ return "";
291
+ try {
292
+ const raizReal = realpathSync(raiz);
293
+ const destino = realpathSync(real);
294
+ if (destino !== raizReal && !destino.startsWith(raizReal + sep))
295
+ return "";
296
+ return readFileSync(destino, "utf8");
297
+ }
298
+ catch {
299
+ return "";
300
+ }
301
+ };
75
302
  const leerPendientes = async () => {
76
303
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
304
  const crudos = collectPending(await agente.getState({
@@ -83,17 +310,26 @@ export async function abrirSesionReal(opciones) {
83
310
  const f = ficheroDe(c);
84
311
  if (f)
85
312
  ficheros.set(c.id, f);
86
- // El ANTES es el disco: el interrupt pausa ANTES de escribir. La ruta viene del
87
- // backend, que es relativo a la raíz del proyecto y si no está en el disco, el
88
- // fichero es nuevo y su «antes» es la cadena vacía, no un error.
89
- const vista = cambioDe(c, (ruta) => {
90
- try {
91
- return readFileSync(resolve(raiz, ruta), "utf8");
92
- }
93
- catch {
94
- return "";
95
- }
96
- });
313
+ // El ANTES es el disco: el interrupt pausa ANTES de escribir. Y si no está en el
314
+ // disco, el fichero es nuevo y su «antes» es la cadena vacía, no un error.
315
+ //
316
+ // La ruta viene del backend del agente, que va con `virtualMode: true`, así que llega
317
+ // ROOTEADA en el proyecto (`/app.xne`) — y esto decía «es relativa a la raíz» y
318
+ // resolvía con `resolve(raiz, ruta)`, que con una absoluta DESCARTA la base. Medido:
319
+ // con la forma rooteada el ANTES salía siempre vacío, así que un fichero EXISTENTE se
320
+ // enseñaba como nuevo y sus líneas quitadas no se veían — en el único momento en que
321
+ // una persona ve lo que el agente va a escribir antes de que exista. Pasó desapercibido
322
+ // porque el test que lo cubría usaba `"app.xne"` sin barra, que es la otra forma que
323
+ // también puede llegar. `rutaRealDeVirtual` acepta las dos y además contiene el `..`,
324
+ // que por aquí traía al diff el contenido de un fichero de FUERA del proyecto.
325
+ //
326
+ // Y la contención lexical NO basta sola: un enlace simbólico DENTRO del proyecto que
327
+ // apunte fuera la pasa —su sitio sí está dentro— y `readFileSync` lo sigue, así que el
328
+ // contenido de fuera acabaría igual en la pantalla de la aprobación. Lo que falla no es
329
+ // el sitio, es el DESTINO: la misma lección que `arbolDeProyecto.ts` ya pagó, y por eso
330
+ // se recomprueba sobre el camino REAL. `realpath` canonicaliza además las mayúsculas,
331
+ // que en APFS y en NTFS es la otra mitad del mismo agujero.
332
+ const vista = cambioDe(c, (ruta) => leerElAntes(ruta));
97
333
  if (vista)
98
334
  diffs.set(c.id, vista.lineas);
99
335
  }
@@ -106,6 +342,15 @@ export async function abrirSesionReal(opciones) {
106
342
  * escribió el turno 1.
107
343
  */
108
344
  const turno = async (peticion, piel) => {
345
+ // Un turno sobre una sesión ya cerrada reviviría un hilo que su dueño soltó al cambiar
346
+ // de proyecto. Falla en vez de trabajar en silencio sobre la raíz equivocada.
347
+ if (cerrada)
348
+ throw new Error("la sesión ya está cerrada");
349
+ // El reloj del turno ENTERO. Se le pasa a cada pasada de `correrTurno` como `desde`:
350
+ // solo la última cierra, y su `fin` tiene que contar desde aquí y no desde que empezó
351
+ // ella — si no, un turno largo con aprobación por medio diría solo lo que tardó la
352
+ // reanudación.
353
+ const t0 = Date.now();
109
354
  const instantanea = await tomarInstantanea(raiz, entorno.git);
110
355
  let payload = { messages: [new HumanMessage(peticion)] };
111
356
  let bitacora = null;
@@ -115,46 +360,395 @@ export async function abrirSesionReal(opciones) {
115
360
  // con las decisiones — también con rejects, que si no se resumen dejan el interrupt
116
361
  // colgado para siempre y el modelo nunca llega a saber que se rechazó.
117
362
  let cortadoPorTope = false;
118
- while (true) {
119
- ronda += 1;
120
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
- const stream = await agente.stream(payload, {
122
- configurable: { thread_id: hilo },
123
- streamMode: ["updates", "messages"],
124
- subgraphs: true,
125
- });
126
- bitacora = await correrTurno(aEventos(stream, async () => (await leerPendientes()).lista), piel, {
127
- avisos: (b) => (b.corrio("verify") ? [] : ["⚠ el verificador no ha corrido en este turno"]),
128
- });
129
- const { lista, ficheros, diffs } = await leerPendientes();
130
- if (lista.length === 0)
131
- break;
132
- // El tope existe porque un modelo que insiste tras cada rechazo convierte esto en un
133
- // ciclo automático de ~200k tokens por ronda (medido en da04). Sin resumir: nada se
134
- // aplica, pero el interrupt queda en el estado.
135
- if (ronda >= MAX_APPROVAL_ROUNDS) {
136
- piel.linea(`\n⚠ tope de ${MAX_APPROVAL_ROUNDS} rondas de aprobación agotado.`);
137
- piel.linea(` quedaban ${lista.length} sin resolver, y NO se han aplicado.`);
138
- cortadoPorTope = true;
139
- break;
363
+ /** Tandas seguidas aprobadas SOLAS —artefactos, o todo si el proyecto está en
364
+ * «sin aprobación»—. Tope propio: ver dónde se usa. */
365
+ let tandasAutomaticas = 0;
366
+ /** Los ficheros del PROYECTO que este turno aplicó sin preguntar. Solo se llena en un
367
+ * proyecto con «sin aprobación»; los artefactos no cuentan aquí, porque un artefacto
368
+ * no es del proyecto y su constancia es su propio acto. */
369
+ const aplicadasSinPreguntar = [];
370
+ /**
371
+ * El estado del lazo, compartido entre el generador y el bucle de rondas.
372
+ *
373
+ * Lo escribe `conVerificacion` mientras la pasada corre, y lo leen el aviso determinista
374
+ * y la decisión de cierre de ESA misma pasada, más el bucle de fuera para saber si hay
375
+ * que reparar. Es la única forma de que quien cierra el turno sepa algo que solo se
376
+ * conoce al agotarse el flujo: si detrás viene otra ronda, otro intento, o nada.
377
+ */
378
+ let rondaEscribio = false;
379
+ let motivoSinVerificar;
380
+ /**
381
+ * Cómo acabó el verificador en la pasada, y sus hallazgos.
382
+ *
383
+ * Viven aquí y no en la bitácora porque la bitácora responde a otra pregunta: `corrio`
384
+ * dice si un nodo pasó, no con qué veredicto —y de esto depende que una tarea se dé
385
+ * por terminada—. Se reinician en CADA ronda, como `rondaEscribio`: lo que vale es lo
386
+ * que dijo la última pasada, que es la que dejó el proyecto como está.
387
+ */
388
+ let veredicto = "no-corrio";
389
+ let hallazgosDelTurno = [];
390
+ /**
391
+ * Cuántos hallazgos quedaron FUERA del reparto. `undefined` mientras el verificador no
392
+ * haya corrido: ausente es «no se midió» y `0` es «no había ningún otro»
393
+ * (`core/entrega.ts#ResultadoDeTurno.preexistentes`). De aquí sale lo que le permite al
394
+ * juez de QA leer `hallazgos` por lo que es: una lista YA filtrada.
395
+ */
396
+ let preexistentesDelTurno;
397
+ /** Si esta pasada cierra el turno. `false` = detrás viene otra ronda o un intento. */
398
+ let cerrarRonda = true;
399
+ /** Si tras esta pasada hay que lanzar un intento de reparación. */
400
+ let reparar = false;
401
+ /** Intentos de reparación ya lanzados en este turno. */
402
+ let intento = 0;
403
+ /** Los hallazgos del último veredicto rojo, para redactar la petición de reparación. */
404
+ let ultimosHallazgos = [];
405
+ /** La huella del veredicto anterior, para detectar que reparar no avanza. */
406
+ let huellaPrevia;
407
+ /**
408
+ * El lazo de verificación, cosido al FINAL del flujo de eventos y no después del turno.
409
+ *
410
+ * Tiene que ir dentro del flujo porque `correrTurno` cierra el turno en su `finally`
411
+ * —el aviso «no ha corrido», y `piel.fin()`— en cuanto el flujo se agota. Verificar
412
+ * después de eso pintaría el veredicto detrás del fin del turno, y en la web el
413
+ * compositor ya se habría encendido con el turno «terminado».
414
+ *
415
+ * Solo verifica la ronda FINAL: la que termina sin escrituras pendientes. En una ronda
416
+ * con aprobaciones por resolver las escrituras no se han aplicado todavía, así que no
417
+ * hay nada que medir — se medirá en la ronda que las aplique.
418
+ *
419
+ * Y solo si el turno tocó ficheros del PROYECTO. Los de `.xonecode/` no cuentan: ahí
420
+ * escribe el propio harness (la memoria, los resúmenes de contexto) y contarlos haría
421
+ * que un turno de pura conversación pasara por el simulador — y que el aviso de «no se
422
+ * verificó» saltara en un «cuéntame un chiste», que es exactamente lo que la bitácora
423
+ * de turno existe para evitar.
424
+ */
425
+ async function* conVerificacion(eventos) {
426
+ // Un intento de reparación se anuncia al EMPEZAR su pasada, no al final de la anterior:
427
+ // así el «🔁 reparando» abre el tramo de trabajo que viene, en vez de cerrar el que
428
+ // acaba. Es el orden que `guionizado.ts` ya recorre.
429
+ if (intento > 0)
430
+ yield { tipo: "reparacion", intento, tope: TOPE_REPARACIONES };
431
+ reparar = false;
432
+ yield* eventos;
433
+ // Lo primero al agotarse la pasada: lo que el agente dejó escrito sin preguntar. Va
434
+ // ANTES de la decisión de cierre y antes del veredicto porque es trabajo TERMINADO de
435
+ // esta pasada — anunciarlo después del `fin` lo pintaría fuera del turno, que es el
436
+ // mismo motivo por el que el verificador está cosido aquí dentro.
437
+ for (const artefacto of artefactosDeLaPasada.splice(0))
438
+ yield { tipo: "artefacto", artefacto };
439
+ if ((await leerPendientes()).lista.length > 0) {
440
+ // Quedan escrituras por aprobar. Si el bucle va a seguir —hay quien apruebe y no se
441
+ // ha agotado el tope de rondas— esta pasada NO es el final del turno y no cierra.
442
+ // Son las mismas dos condiciones del `break` del bucle, y tienen que serlo: si
443
+ // divergen, un turno se queda sin `fin` o cierra dos veces.
444
+ cerrarRonda = !(ronda < topeDeRondas && opciones.pedirAprobacion !== undefined);
445
+ return;
446
+ }
447
+ cerrarRonda = true;
448
+ const cambios = (await instantanea.cambios()).filter((c) => c.clase !== "borrado" && !c.ruta.startsWith(".xonecode/") && c.ruta !== ".xonecode");
449
+ rondaEscribio = cambios.length > 0;
450
+ if (!rondaEscribio) {
451
+ // No hay nada que verificar, y eso NO es un verde. Se DICE, porque este motivo
452
+ // acaba en la tarjeta de una tarea aparcada y «no se sabe» tiene que distinguirse
453
+ // de «el simulador no está» y de «el turno se cortó antes de llegar».
454
+ motivoSinVerificar = "el turno no escribió ningún fichero del proyecto";
455
+ return;
456
+ }
457
+ if (opciones.verifier === undefined) {
458
+ motivoSinVerificar = "esta ejecución no tiene verificador";
459
+ return;
460
+ }
461
+ yield { tipo: "fase", fase: "verificando" };
462
+ let informe;
463
+ try {
464
+ informe = await opciones.verifier.verificar(raiz);
140
465
  }
141
- if (!opciones.pedirAprobacion) {
142
- piel.linea(`\n⏸ ${lista.length} escritura(s) piden aprobación, y no hay quién apruebe.`);
143
- piel.linea(" Nada se ha aprobado y nada se ha aplicado.");
144
- break;
466
+ catch (e) {
467
+ // Que no esté el binario NO es un fallo del proyecto, y se dice como tal. El aviso
468
+ // determinista del final saldrá igualmente: «no ha corrido» sigue siendo verdad.
469
+ motivoSinVerificar = e instanceof Error ? e.message : String(e);
470
+ yield { tipo: "aviso", texto: `⚠ no se pudo verificar: ${motivoSinVerificar}`, severidad: "aviso" };
471
+ return;
145
472
  }
146
- const decisiones = await opciones.pedirAprobacion(lista, ficheros, diffs);
147
- payload = new Command({ resume: buildResume(decisiones) });
473
+ // Los hallazgos se reparten entre los ficheros que ESTE turno tocó y los demás. El
474
+ // simulador mira el proyecto entero —es su API—, y un error que ya estaba en un
475
+ // fichero que el agente no abrió no es del agente. Un hallazgo sin fichero no se
476
+ // puede atribuir: se enseña con los del turno, que es el lado conservador.
477
+ const tocados = new Set(cambios.map((c) => resolverRuta(raiz, c.ruta)));
478
+ const delTurno = informe.hallazgos.filter((h) => h.fichero === undefined || tocados.has(resolverRuta(h.fichero)));
479
+ const preexistentes = informe.hallazgos.length - delTurno.length;
480
+ const errores = delTurno.filter((h) => h.severidad === "error").length;
481
+ const hallazgos = delTurno.map((h) => ({
482
+ code: h.code,
483
+ severidad: h.severidad,
484
+ mensaje: h.mensaje,
485
+ ...(h.fichero === undefined ? {} : { fichero: relative(raiz, h.fichero) }),
486
+ ...(h.linea === undefined ? {} : { linea: h.linea }),
487
+ }));
488
+ // Lo mismo que va al evento, apuntado para el retorno: NO se recalcula ni se
489
+ // re-parsea de la bitácora, que es cómo dos copias de una cuenta acaban discrepando.
490
+ veredicto = errores === 0 ? "verde" : "rojo";
491
+ hallazgosDelTurno = hallazgos;
492
+ // El reparto entero, no solo su mitad. Aquí sí se apunta el cero: el evento lo omite
493
+ // porque una línea de consola que dice «y 0 más» no dice nada, pero quien recibe
494
+ // `hallazgos` necesita saber que la lista está filtrada — y con cero también.
495
+ preexistentesDelTurno = preexistentes;
496
+ yield {
497
+ tipo: "verificacion",
498
+ verde: errores === 0,
499
+ errores,
500
+ avisos: delTurno.length - errores,
501
+ hallazgos,
502
+ ...(preexistentes > 0 ? { preexistentes } : {}),
503
+ };
504
+ if (errores === 0)
505
+ return;
506
+ // Rojo. Tres salidas, y solo una de ellas es «inténtalo otra vez».
507
+ //
508
+ // La huella son los ERRORES, no todos los hallazgos: un aviso que va y viene no dice
509
+ // nada de si el error se está arreglando. Y se compara con la del veredicto anterior
510
+ // y no con «¿bajó el número?»: dos errores distintos en vez de dos iguales también es
511
+ // avance, y un modelo que arregla uno y rompe otro no debe quedarse bloqueado como si
512
+ // no hubiera hecho nada.
513
+ const huella = hallazgos
514
+ .filter((h) => h.severidad === "error")
515
+ .map((h) => `${h.code}|${h.fichero ?? ""}|${h.linea ?? ""}`)
516
+ .sort()
517
+ .join("\n");
518
+ if (huella === huellaPrevia) {
519
+ // Corregir no cambió nada: el mismo error, en el mismo sitio. Seguir sería gastar
520
+ // el tope —y aprobaciones humanas— en repetir el mismo cambio.
521
+ yield {
522
+ tipo: "bloqueado",
523
+ motivo: "no-progreso",
524
+ explicacion: `el intento ${intento} dejó los mismos ${errores} error(es) en los mismos sitios`,
525
+ };
526
+ return;
527
+ }
528
+ if (intento >= TOPE_REPARACIONES) {
529
+ yield {
530
+ tipo: "bloqueado",
531
+ motivo: "tope-reparaciones",
532
+ explicacion: `tras ${intento} intento(s) siguen ${errores} error(es); se deja como está para que lo mires`,
533
+ };
534
+ return;
535
+ }
536
+ // Se intenta otra vez. Esta pasada NO cierra el turno: el `fin` y los avisos van al
537
+ // final del intento que viene, o del que corte.
538
+ huellaPrevia = huella;
539
+ ultimosHallazgos = hallazgos;
540
+ intento += 1;
541
+ reparar = true;
542
+ cerrarRonda = false;
148
543
  }
544
+ /**
545
+ * La petición de reparación: los hallazgos tal cual, sin más interpretación.
546
+ *
547
+ * Va como un mensaje de USUARIO en el mismo hilo, no como un prompt de sistema nuevo:
548
+ * es la única forma de que el especialista vea lo que acaba de escribir y el error
549
+ * juntos. Y no se le dice CÓMO arreglarlo —eso lo sabe él o no lo sabe—, se le recuerda
550
+ * lo único que importa aquí: que no se invente nada para que el error desaparezca, que
551
+ * es justo lo que XOne no le va a reprochar y el simulador sí.
552
+ */
553
+ const peticionDeReparacion = () => [
554
+ `El simulador de XOne ha revisado lo que acabas de escribir y ha encontrado ${ultimosHallazgos.filter((h) => h.severidad === "error").length} error(es):`,
555
+ ...ultimosHallazgos.map((h) => `- ${h.severidad === "error" ? "ERROR" : "aviso"} ${h.code}${h.fichero === undefined ? "" : ` en ${h.fichero}${h.linea === undefined ? "" : `:${h.linea}`}`}: ${h.mensaje}`),
556
+ "",
557
+ "Corrige los errores. No inventes atributos, funciones ni propiedades para que",
558
+ "desaparezcan: XOne ignora lo desconocido en silencio y el simulador lo detecta.",
559
+ "Si algún error no sabes cómo corregirlo, dilo en vez de intentar otra cosa.",
560
+ ].join("\n");
561
+ // Dos bucles anidados y a propósito: el de dentro son las RONDAS de aprobación de una
562
+ // petición (una pausa termina la ronda, se reanuda con las decisiones); el de fuera son
563
+ // los INTENTOS de reparación, cada uno una petición nueva en el mismo hilo. Quién
564
+ // decide si hay intento es el generador, que es quien ve el veredicto.
565
+ do {
566
+ if (intento > 0)
567
+ payload = { messages: [new HumanMessage(peticionDeReparacion())] };
568
+ ronda = 0;
569
+ while (true) {
570
+ ronda += 1;
571
+ rondaEscribio = false;
572
+ motivoSinVerificar = undefined;
573
+ veredicto = "no-corrio";
574
+ hallazgosDelTurno = [];
575
+ preexistentesDelTurno = undefined;
576
+ cerrarRonda = true;
577
+ const aborto = new AbortController();
578
+ cancelarEnCurso = () => aborto.abort(new Error("turno cancelado por el usuario"));
579
+ try {
580
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
581
+ const stream = await agente.stream(payload, {
582
+ configurable: { thread_id: hilo },
583
+ signal: aborto.signal,
584
+ streamMode: ["updates", "messages"],
585
+ subgraphs: true,
586
+ });
587
+ bitacora = await correrTurno(conVerificacion(
588
+ // El flujo del grafo MÁS lo que el agente externo vaya haciendo en su propio
589
+ // proceso: sin esto, ni una de sus tools cruza y son minutos de pantalla quieta.
590
+ entrelazar(aEventos(stream, async () => (await leerPendientes()).lista, ({ nombre, detalle, parametros }) => diagnostico?.herramienta(nombre, detalle, parametros, tracker)), eventosExternos)), piel, {
591
+ // El aviso solo si el turno ESCRIBIÓ y aun así no se verificó. Antes saltaba en
592
+ // todos los turnos, incluido «cuéntame un chiste» — y un aviso que salta cuando
593
+ // no ha pasado nada enseña a ignorarlo, que es lo contrario de lo que se compra
594
+ // con él. Con el motivo, porque «no ha corrido» sin más manda a adivinar.
595
+ avisos: (b) => [
596
+ ...(b.corrio("verify") || !rondaEscribio
597
+ ? []
598
+ : [
599
+ `⚠ el verificador no ha corrido en este turno${motivoSinVerificar === undefined ? "" : ` (${motivoSinVerificar})`}`,
600
+ ]),
601
+ // Lo que se aplicó sin que nadie lo mirara, CON LOS NOMBRES. La decisión se
602
+ // tomó una vez, quizá hace meses, en `settings.json`; el turno que la ejerce
603
+ // es el único momento en que se puede recordar. Solo si hubo alguna: un aviso
604
+ // que salta cuando no ha pasado nada enseña a ignorarlo.
605
+ ...(aplicadasSinPreguntar.length === 0
606
+ ? []
607
+ : [
608
+ `⚠ ${aplicadasSinPreguntar.length} escritura(s) aplicadas SIN aprobación: ${aplicadasSinPreguntar.join(", ")}` +
609
+ " — este proyecto está en «sin aprobación» (/aprobacion humana lo deshace)",
610
+ ]),
611
+ ],
612
+ // Solo la ÚLTIMA pasada cierra el turno. Lo decide el generador al agotar el
613
+ // flujo: si quedan rondas o viene un intento, no hay `fin` todavía.
614
+ cerrar: () => cerrarRonda,
615
+ desde: t0,
616
+ });
617
+ }
618
+ finally {
619
+ cancelarEnCurso = undefined;
620
+ }
621
+ const { lista, ficheros, diffs } = await leerPendientes();
622
+ if (lista.length === 0)
623
+ break;
624
+ /**
625
+ * Los artefactos se aprueban SOLOS, y el resto sigue pidiendo permiso.
626
+ *
627
+ * La aprobación existe para proteger el proyecto del usuario, y un artefacto ya no lo
628
+ * toca: vive en la carpeta de la sesión, no entra en git y no sube a CloudStudio. Que
629
+ * dibujar un diagrama costara tres clics enseñaría a aprobar sin mirar, que es como se
630
+ * rompe la aprobación justo cuando importa. A cambio se ANUNCIA (evento `artefacto`):
631
+ * una escritura que nadie aprueba no puede ser además muda.
632
+ *
633
+ * `esRutaDeArtefacto` es una lista blanca de forma y no un `startsWith`, porque de
634
+ * ella depende que esto no sea un camino para escribir en el proyecto sin permiso.
635
+ */
636
+ const todoAutomatico = opciones.sinAprobacion?.() === true;
637
+ const automaticas = new Map();
638
+ const humanos = [];
639
+ for (const p of lista) {
640
+ const ruta = ficheros.get(p.id);
641
+ if (esRutaDeArtefacto(ruta)) {
642
+ automaticas.set(p.id, { type: "approve" });
643
+ }
644
+ else if (todoAutomatico) {
645
+ // El proyecto está en «sin aprobación». Se apunta la RUTA, que es lo que después
646
+ // dice el aviso de honestidad: un contador sin nombres es el aviso que enseña a
647
+ // ignorar los avisos, que es justo lo que la bitácora existe para evitar.
648
+ automaticas.set(p.id, { type: "approve" });
649
+ aplicadasSinPreguntar.push(ruta ?? p.descripcion);
650
+ }
651
+ else {
652
+ humanos.push(p);
653
+ }
654
+ }
655
+ if (humanos.length === 0) {
656
+ // Ronda que no gastó a nadie: no cuenta para el tope de APROBACIÓN. Contarla haría
657
+ // que cinco diagramas cortaran el turno con `cortadoPorTope`, cuyo significado
658
+ // —«quedaron escrituras esperando aprobación»— sería falso. La predicción de cierre
659
+ // de la pasada usó el mismo `ronda`, y sigue siendo correcta: viene otra pasada.
660
+ //
661
+ // Pero tiene su PROPIO tope, y por el mismo motivo que el otro: cada pasada es una
662
+ // llamada al modelo, y un agente que escribe un artefacto por pasada sería un bucle
663
+ // que nadie corta —aquí no hay humano al que preguntar, que es justo lo que frena
664
+ // al otro—. Al agotarse se para y se dice, con `cortadoPorTope` puesto: quedaron
665
+ // escrituras sin aplicar, que es lo que ese código de salida significa.
666
+ tandasAutomaticas += 1;
667
+ if (tandasAutomaticas > topeDeRondas) {
668
+ // El texto dice de QUÉ tandas habla: con «sin aprobación» puesto, por aquí pasan
669
+ // también las escrituras del proyecto, y llamarlas «artefactos» sería mentir en
670
+ // el único mensaje que explica por qué el turno se cortó.
671
+ const que = todoAutomatico ? "escrituras sin aprobación" : "artefactos";
672
+ piel.linea(`\n⚠ tope de ${topeDeRondas} tandas de ${que} agotado en este turno.`);
673
+ piel.linea(` quedaban ${lista.length} sin escribir, y NO se han aplicado.`);
674
+ cortadoPorTope = true;
675
+ break;
676
+ }
677
+ ronda -= 1;
678
+ payload = new Command({ resume: buildResume(automaticas) });
679
+ continue;
680
+ }
681
+ // El tope existe porque un modelo que insiste tras cada rechazo convierte esto en un
682
+ // ciclo automático de ~200k tokens por ronda (medido en da04). Sin resumir: nada se
683
+ // aplica, pero el interrupt queda en el estado.
684
+ if (ronda >= topeDeRondas) {
685
+ piel.linea(`\n⚠ tope de ${topeDeRondas} rondas de aprobación agotado.`);
686
+ piel.linea(` quedaban ${humanos.length} sin resolver, y NO se han aplicado.`);
687
+ cortadoPorTope = true;
688
+ break;
689
+ }
690
+ if (!opciones.pedirAprobacion) {
691
+ piel.linea(`\n⏸ ${humanos.length} escritura(s) piden aprobación, y no hay quién apruebe.`);
692
+ piel.linea(" Nada se ha aprobado y nada se ha aplicado.");
693
+ break;
694
+ }
695
+ let decisiones;
696
+ try {
697
+ decisiones = await opciones.pedirAprobacion(humanos, ficheros, diffs);
698
+ }
699
+ catch (e) {
700
+ // La ronda que acaba de pasar no cerró el turno —predijo que habría reanudación— y
701
+ // la aprobación ha reventado en vez de contestar (el «sin humano» de `run.ts`, que
702
+ // corta desde dentro). Sin esto, ese turno se quedaba sin `fin`: sin línea de tiempo
703
+ // en stdio y sin plegar en la web. Los avisos no se pierden: con escrituras
704
+ // pendientes ninguno aplica. Se cierra y se propaga, que sigue siendo un fallo.
705
+ if (!cerrarRonda)
706
+ piel.fin(Date.now() - t0);
707
+ throw e;
708
+ }
709
+ // Las dos mitades vuelven en UN solo resume: un interrupt que no se resume se queda
710
+ // colgado para siempre, así que los artefactos aprobados solos tienen que viajar con
711
+ // las decisiones humanas y no en una reanudación aparte.
712
+ payload = new Command({ resume: buildResume(new Map([...automaticas, ...decisiones])) });
713
+ }
714
+ } while (reparar);
149
715
  // El diff contra la foto de ESTE turno. Un turno que no tocó nada TIENE que verse igual.
150
716
  const cambios = await instantanea.cambios();
717
+ /**
718
+ * Las escrituras que quedaron esperando aprobación, MEDIDAS y no recordadas.
719
+ *
720
+ * Se le pregunta al estado del grafo una vez, aquí, en vez de apuntarlas en cada
721
+ * `break`: hay CUATRO salidas del bucle y tres de ellas usan variables distintas
722
+ * (`lista.length` en el tope de tandas, `humanos.length` en el de rondas y en el «no
723
+ * hay quién apruebe»), así que una contabilidad repartida es una contabilidad que
724
+ * deriva. Y no se deduce de `cortadoPorTope`: por «no hay quién apruebe» se sale sin
725
+ * ponerlo y también quedan escrituras colgando.
726
+ */
727
+ const pendientes = (await leerPendientes()).lista.length;
151
728
  // `cortadoPorTope` viaja en el retorno y no en la bitácora porque es lo que decide el
152
729
  // CÓDIGO DE SALIDA de quien invoca: un turno que se quedó con escrituras sin resolver
153
- // no es un éxito, y CI no puede leerlo como tal.
154
- return { bitacora: bitacora, cambios, cortadoPorTope };
730
+ // no es un éxito, y CI no puede leerlo como tal. Y con él viaja ahora lo que decide si
731
+ // una TAREA se puede dar por terminada (`core/entrega.ts`), que hasta ahora se tiraba.
732
+ return {
733
+ bitacora: bitacora,
734
+ cambios,
735
+ cortadoPorTope,
736
+ verificador: veredicto,
737
+ pendientes,
738
+ // Ausente y vacío: sin hallazgos el campo no se pone, para que quien lo lea no
739
+ // confunda «no hubo» con «no se sabe».
740
+ ...(hallazgosDelTurno.length === 0 ? {} : { hallazgos: hallazgosDelTurno }),
741
+ ...(preexistentesDelTurno === undefined ? {} : { preexistentes: preexistentesDelTurno }),
742
+ ...(motivoSinVerificar === undefined ? {} : { motivoSinVerificar }),
743
+ };
155
744
  };
156
745
  return {
157
746
  turno,
747
+ cancelar: () => cancelarEnCurso?.(),
748
+ cerrar: () => {
749
+ cerrada = true;
750
+ cancelarEnCurso?.();
751
+ },
158
752
  /** Para `/modelo`: agente nuevo con los mismos hilo, checkpointer y tracker. */
159
753
  async cambiarModelos(nuevos) {
160
754
  modelos = nuevos;
@@ -165,6 +759,25 @@ export async function abrirSesionReal(opciones) {
165
759
  hilo = id ?? `xonecode-${randomUUID()}`;
166
760
  },
167
761
  tracker,
762
+ /**
763
+ * Lo consumido por la SESIÓN, en sus dos cuentas. Se lee, no se guarda: el tracker y el
764
+ * acumulador viven en este cierre y son la única fuente.
765
+ */
766
+ consumo: () => ({
767
+ modelo: { entrada: tracker.input, salida: tracker.output, cache: tracker.cache },
768
+ externo: consumoExterno,
769
+ // La entrada de la ÚLTIMA llamada, que es otra pregunta: ver el campo en su tipo.
770
+ contexto: tracker.contexto,
771
+ }),
772
+ /** Avisa cuando cualquiera de las dos cuentas cambia. Devuelve cómo dejar de escuchar. */
773
+ alCambiarConsumo: (oyente) => {
774
+ oyentesDeConsumo.push(oyente);
775
+ return () => {
776
+ const i = oyentesDeConsumo.indexOf(oyente);
777
+ if (i >= 0)
778
+ oyentesDeConsumo.splice(i, 1);
779
+ };
780
+ },
168
781
  get hilo() {
169
782
  return hilo;
170
783
  },