@hoilab/ada-cli 0.84.22 → 0.85.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 (1114) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/README.es.md +17 -6
  3. package/README.md +109 -11
  4. package/dist/ada-publico.d.ts +35 -0
  5. package/dist/ada-publico.d.ts.map +1 -0
  6. package/dist/ada-publico.js +40 -0
  7. package/dist/ada-publico.js.map +1 -0
  8. package/dist/cli/audit-command.d.ts +15 -0
  9. package/dist/cli/audit-command.d.ts.map +1 -0
  10. package/dist/cli/audit-command.js +280 -0
  11. package/dist/cli/audit-command.js.map +1 -0
  12. package/dist/cli/banco-command.d.ts +15 -0
  13. package/dist/cli/banco-command.d.ts.map +1 -0
  14. package/dist/cli/banco-command.js +213 -0
  15. package/dist/cli/banco-command.js.map +1 -0
  16. package/dist/cli/cleanse-command.d.ts +11 -0
  17. package/dist/cli/cleanse-command.d.ts.map +1 -0
  18. package/dist/cli/cleanse-command.js +101 -0
  19. package/dist/cli/cleanse-command.js.map +1 -0
  20. package/dist/cli/commit-command.d.ts +31 -0
  21. package/dist/cli/commit-command.d.ts.map +1 -0
  22. package/dist/cli/commit-command.js +158 -0
  23. package/dist/cli/commit-command.js.map +1 -0
  24. package/dist/cli/computer-command.d.ts +12 -0
  25. package/dist/cli/computer-command.d.ts.map +1 -0
  26. package/dist/cli/computer-command.js +141 -0
  27. package/dist/cli/computer-command.js.map +1 -0
  28. package/dist/cli/discover-command.d.ts +18 -0
  29. package/dist/cli/discover-command.d.ts.map +1 -0
  30. package/dist/cli/discover-command.js +108 -0
  31. package/dist/cli/discover-command.js.map +1 -0
  32. package/dist/cli/hashline-command.d.ts +11 -0
  33. package/dist/cli/hashline-command.d.ts.map +1 -0
  34. package/dist/cli/hashline-command.js +106 -0
  35. package/dist/cli/hashline-command.js.map +1 -0
  36. package/dist/cli/import-command.d.ts +35 -0
  37. package/dist/cli/import-command.d.ts.map +1 -0
  38. package/dist/cli/import-command.js +164 -0
  39. package/dist/cli/import-command.js.map +1 -0
  40. package/dist/cli/politica-command.d.ts +20 -0
  41. package/dist/cli/politica-command.d.ts.map +1 -0
  42. package/dist/cli/politica-command.js +93 -0
  43. package/dist/cli/politica-command.js.map +1 -0
  44. package/dist/cli/refine-command.d.ts +18 -0
  45. package/dist/cli/refine-command.d.ts.map +1 -0
  46. package/dist/cli/refine-command.js +101 -0
  47. package/dist/cli/refine-command.js.map +1 -0
  48. package/dist/cli/review-command.d.ts +24 -0
  49. package/dist/cli/review-command.d.ts.map +1 -0
  50. package/dist/cli/review-command.js +168 -0
  51. package/dist/cli/review-command.js.map +1 -0
  52. package/dist/cli/schedule-command.d.ts +17 -0
  53. package/dist/cli/schedule-command.d.ts.map +1 -0
  54. package/dist/cli/schedule-command.js +117 -0
  55. package/dist/cli/schedule-command.js.map +1 -0
  56. package/dist/cli/serve-command.d.ts +72 -0
  57. package/dist/cli/serve-command.d.ts.map +1 -0
  58. package/dist/cli/serve-command.js +568 -0
  59. package/dist/cli/serve-command.js.map +1 -0
  60. package/dist/cli/telegram-command.d.ts +18 -0
  61. package/dist/cli/telegram-command.d.ts.map +1 -0
  62. package/dist/cli/telegram-command.js +137 -0
  63. package/dist/cli/telegram-command.js.map +1 -0
  64. package/dist/cli/telemetria-command.d.ts +22 -0
  65. package/dist/cli/telemetria-command.d.ts.map +1 -0
  66. package/dist/cli/telemetria-command.js +127 -0
  67. package/dist/cli/telemetria-command.js.map +1 -0
  68. package/dist/cli/undo-command.d.ts +9 -0
  69. package/dist/cli/undo-command.d.ts.map +1 -0
  70. package/dist/cli/undo-command.js +56 -0
  71. package/dist/cli/undo-command.js.map +1 -0
  72. package/dist/cli/usage-command.d.ts +10 -0
  73. package/dist/cli/usage-command.d.ts.map +1 -0
  74. package/dist/cli/usage-command.js +209 -0
  75. package/dist/cli/usage-command.js.map +1 -0
  76. package/dist/cli/wiki-command.d.ts +9 -0
  77. package/dist/cli/wiki-command.d.ts.map +1 -0
  78. package/dist/cli/wiki-command.js +76 -0
  79. package/dist/cli/wiki-command.js.map +1 -0
  80. package/dist/cli.d.ts +6 -0
  81. package/dist/cli.d.ts.map +1 -1
  82. package/dist/cli.js +10 -11
  83. package/dist/cli.js.map +1 -1
  84. package/dist/config.d.ts +1 -1
  85. package/dist/config.d.ts.map +1 -1
  86. package/dist/config.js +1 -1
  87. package/dist/config.js.map +1 -1
  88. package/dist/core/ada.d.ts +58 -0
  89. package/dist/core/ada.d.ts.map +1 -0
  90. package/dist/core/ada.js +61 -0
  91. package/dist/core/ada.js.map +1 -0
  92. package/dist/core/advisor/guarda.d.ts +66 -0
  93. package/dist/core/advisor/guarda.d.ts.map +1 -0
  94. package/dist/core/advisor/guarda.js +191 -0
  95. package/dist/core/advisor/guarda.js.map +1 -0
  96. package/dist/core/advisor/runtime.d.ts +66 -0
  97. package/dist/core/advisor/runtime.d.ts.map +1 -0
  98. package/dist/core/advisor/runtime.js +121 -0
  99. package/dist/core/advisor/runtime.js.map +1 -0
  100. package/dist/core/advisor/sesion.d.ts +109 -0
  101. package/dist/core/advisor/sesion.d.ts.map +1 -0
  102. package/dist/core/advisor/sesion.js +139 -0
  103. package/dist/core/advisor/sesion.js.map +1 -0
  104. package/dist/core/agent-session.d.ts +30 -2
  105. package/dist/core/agent-session.d.ts.map +1 -1
  106. package/dist/core/agent-session.js +430 -22
  107. package/dist/core/agent-session.js.map +1 -1
  108. package/dist/core/agents/loader.d.ts +77 -0
  109. package/dist/core/agents/loader.d.ts.map +1 -0
  110. package/dist/core/agents/loader.js +154 -0
  111. package/dist/core/agents/loader.js.map +1 -0
  112. package/dist/core/aprobacion/generados.d.ts +37 -0
  113. package/dist/core/aprobacion/generados.d.ts.map +1 -0
  114. package/dist/core/aprobacion/generados.js +85 -0
  115. package/dist/core/aprobacion/generados.js.map +1 -0
  116. package/dist/core/aprobacion/politica-de-maquina.d.ts +90 -0
  117. package/dist/core/aprobacion/politica-de-maquina.d.ts.map +1 -0
  118. package/dist/core/aprobacion/politica-de-maquina.js +212 -0
  119. package/dist/core/aprobacion/politica-de-maquina.js.map +1 -0
  120. package/dist/core/aprobacion/politica.d.ts +90 -0
  121. package/dist/core/aprobacion/politica.d.ts.map +1 -0
  122. package/dist/core/aprobacion/politica.js +375 -0
  123. package/dist/core/aprobacion/politica.js.map +1 -0
  124. package/dist/core/arranque/atajos.d.ts +46 -0
  125. package/dist/core/arranque/atajos.d.ts.map +1 -0
  126. package/dist/core/arranque/atajos.js +113 -0
  127. package/dist/core/arranque/atajos.js.map +1 -0
  128. package/dist/core/audit/exportacion.d.ts +54 -0
  129. package/dist/core/audit/exportacion.d.ts.map +1 -0
  130. package/dist/core/audit/exportacion.js +265 -0
  131. package/dist/core/audit/exportacion.js.map +1 -0
  132. package/dist/core/audit/libro.d.ts +318 -0
  133. package/dist/core/audit/libro.d.ts.map +1 -0
  134. package/dist/core/audit/libro.js +508 -0
  135. package/dist/core/audit/libro.js.map +1 -0
  136. package/dist/core/audit/registro-sesion.d.ts +78 -0
  137. package/dist/core/audit/registro-sesion.d.ts.map +1 -0
  138. package/dist/core/audit/registro-sesion.js +202 -0
  139. package/dist/core/audit/registro-sesion.js.map +1 -0
  140. package/dist/core/audit/retencion.d.ts +82 -0
  141. package/dist/core/audit/retencion.d.ts.map +1 -0
  142. package/dist/core/audit/retencion.js +152 -0
  143. package/dist/core/audit/retencion.js.map +1 -0
  144. package/dist/core/audit/rutas.d.ts +23 -0
  145. package/dist/core/audit/rutas.d.ts.map +1 -0
  146. package/dist/core/audit/rutas.js +23 -0
  147. package/dist/core/audit/rutas.js.map +1 -0
  148. package/dist/core/auth-storage.d.ts +0 -3
  149. package/dist/core/auth-storage.d.ts.map +1 -1
  150. package/dist/core/auth-storage.js +17 -5
  151. package/dist/core/auth-storage.js.map +1 -1
  152. package/dist/core/banco/almacenamiento.d.ts +18 -0
  153. package/dist/core/banco/almacenamiento.d.ts.map +1 -0
  154. package/dist/core/banco/almacenamiento.js +50 -0
  155. package/dist/core/banco/almacenamiento.js.map +1 -0
  156. package/dist/core/banco/analisis.d.ts +37 -0
  157. package/dist/core/banco/analisis.d.ts.map +1 -0
  158. package/dist/core/banco/analisis.js +164 -0
  159. package/dist/core/banco/analisis.js.map +1 -0
  160. package/dist/core/banco/bloques.d.ts +39 -0
  161. package/dist/core/banco/bloques.d.ts.map +1 -0
  162. package/dist/core/banco/bloques.js +84 -0
  163. package/dist/core/banco/bloques.js.map +1 -0
  164. package/dist/core/banco/cargas.d.ts +84 -0
  165. package/dist/core/banco/cargas.d.ts.map +1 -0
  166. package/dist/core/banco/cargas.js +520 -0
  167. package/dist/core/banco/cargas.js.map +1 -0
  168. package/dist/core/banco/comparar.d.ts +29 -0
  169. package/dist/core/banco/comparar.d.ts.map +1 -0
  170. package/dist/core/banco/comparar.js +28 -0
  171. package/dist/core/banco/comparar.js.map +1 -0
  172. package/dist/core/banco/ejecutar.d.ts +44 -0
  173. package/dist/core/banco/ejecutar.d.ts.map +1 -0
  174. package/dist/core/banco/ejecutar.js +183 -0
  175. package/dist/core/banco/ejecutar.js.map +1 -0
  176. package/dist/core/banco/generar.d.ts +24 -0
  177. package/dist/core/banco/generar.d.ts.map +1 -0
  178. package/dist/core/banco/generar.js +159 -0
  179. package/dist/core/banco/generar.js.map +1 -0
  180. package/dist/core/banco/mutaciones.d.ts +25 -0
  181. package/dist/core/banco/mutaciones.d.ts.map +1 -0
  182. package/dist/core/banco/mutaciones.js +215 -0
  183. package/dist/core/banco/mutaciones.js.map +1 -0
  184. package/dist/core/banco/tipos.d.ts +121 -0
  185. package/dist/core/banco/tipos.d.ts.map +1 -0
  186. package/dist/core/banco/tipos.js +16 -0
  187. package/dist/core/banco/tipos.js.map +1 -0
  188. package/dist/core/blobs/almacen.d.ts +81 -0
  189. package/dist/core/blobs/almacen.d.ts.map +1 -0
  190. package/dist/core/blobs/almacen.js +198 -0
  191. package/dist/core/blobs/almacen.js.map +1 -0
  192. package/dist/core/blobs/broker.d.ts +103 -0
  193. package/dist/core/blobs/broker.d.ts.map +1 -0
  194. package/dist/core/blobs/broker.js +204 -0
  195. package/dist/core/blobs/broker.js.map +1 -0
  196. package/dist/core/blobs/index.d.ts +70 -0
  197. package/dist/core/blobs/index.d.ts.map +1 -0
  198. package/dist/core/blobs/index.js +232 -0
  199. package/dist/core/blobs/index.js.map +1 -0
  200. package/dist/core/blobs/payload.d.ts +55 -0
  201. package/dist/core/blobs/payload.d.ts.map +1 -0
  202. package/dist/core/blobs/payload.js +255 -0
  203. package/dist/core/blobs/payload.js.map +1 -0
  204. package/dist/core/blobs/proveedor.d.ts +85 -0
  205. package/dist/core/blobs/proveedor.d.ts.map +1 -0
  206. package/dist/core/blobs/proveedor.js +196 -0
  207. package/dist/core/blobs/proveedor.js.map +1 -0
  208. package/dist/core/browser/cdp.d.ts +41 -0
  209. package/dist/core/browser/cdp.d.ts.map +1 -0
  210. package/dist/core/browser/cdp.js +119 -0
  211. package/dist/core/browser/cdp.js.map +1 -0
  212. package/dist/core/browser/politica.d.ts +51 -0
  213. package/dist/core/browser/politica.d.ts.map +1 -0
  214. package/dist/core/browser/politica.js +123 -0
  215. package/dist/core/browser/politica.js.map +1 -0
  216. package/dist/core/browser/sesion.d.ts +52 -0
  217. package/dist/core/browser/sesion.d.ts.map +1 -0
  218. package/dist/core/browser/sesion.js +176 -0
  219. package/dist/core/browser/sesion.js.map +1 -0
  220. package/dist/core/browser/tools.d.ts +16 -0
  221. package/dist/core/browser/tools.d.ts.map +1 -0
  222. package/dist/core/browser/tools.js +279 -0
  223. package/dist/core/browser/tools.js.map +1 -0
  224. package/dist/core/commit/diff.d.ts +56 -0
  225. package/dist/core/commit/diff.d.ts.map +1 -0
  226. package/dist/core/commit/diff.js +145 -0
  227. package/dist/core/commit/diff.js.map +1 -0
  228. package/dist/core/commit/mensaje.d.ts +80 -0
  229. package/dist/core/commit/mensaje.d.ts.map +1 -0
  230. package/dist/core/commit/mensaje.js +207 -0
  231. package/dist/core/commit/mensaje.js.map +1 -0
  232. package/dist/core/compactar/index.d.ts +52 -0
  233. package/dist/core/compactar/index.d.ts.map +1 -0
  234. package/dist/core/compactar/index.js +91 -0
  235. package/dist/core/compactar/index.js.map +1 -0
  236. package/dist/core/compactar/registro-denso.d.ts +160 -0
  237. package/dist/core/compactar/registro-denso.d.ts.map +1 -0
  238. package/dist/core/compactar/registro-denso.js +620 -0
  239. package/dist/core/compactar/registro-denso.js.map +1 -0
  240. package/dist/core/computer/captura.d.ts +35 -0
  241. package/dist/core/computer/captura.d.ts.map +1 -0
  242. package/dist/core/computer/captura.js +140 -0
  243. package/dist/core/computer/captura.js.map +1 -0
  244. package/dist/core/computer/driver.d.ts +130 -0
  245. package/dist/core/computer/driver.d.ts.map +1 -0
  246. package/dist/core/computer/driver.js +206 -0
  247. package/dist/core/computer/driver.js.map +1 -0
  248. package/dist/core/computer/motor/actions.d.ts +109 -0
  249. package/dist/core/computer/motor/actions.d.ts.map +1 -0
  250. package/dist/core/computer/motor/actions.js +157 -0
  251. package/dist/core/computer/motor/actions.js.map +1 -0
  252. package/dist/core/computer/motor/bridge.d.ts +232 -0
  253. package/dist/core/computer/motor/bridge.d.ts.map +1 -0
  254. package/dist/core/computer/motor/bridge.js +2335 -0
  255. package/dist/core/computer/motor/bridge.js.map +1 -0
  256. package/dist/core/computer/motor/cdp.d.ts +107 -0
  257. package/dist/core/computer/motor/cdp.d.ts.map +1 -0
  258. package/dist/core/computer/motor/cdp.js +659 -0
  259. package/dist/core/computer/motor/cdp.js.map +1 -0
  260. package/dist/core/computer/motor/config.d.ts +23 -0
  261. package/dist/core/computer/motor/config.d.ts.map +1 -0
  262. package/dist/core/computer/motor/config.js +100 -0
  263. package/dist/core/computer/motor/config.js.map +1 -0
  264. package/dist/core/computer/motor/contract.d.ts +81 -0
  265. package/dist/core/computer/motor/contract.d.ts.map +1 -0
  266. package/dist/core/computer/motor/contract.js +14 -0
  267. package/dist/core/computer/motor/contract.js.map +1 -0
  268. package/dist/core/computer/motor/note.d.ts +37 -0
  269. package/dist/core/computer/motor/note.d.ts.map +1 -0
  270. package/dist/core/computer/motor/note.js +162 -0
  271. package/dist/core/computer/motor/note.js.map +1 -0
  272. package/dist/core/computer/motor/outline.d.ts +155 -0
  273. package/dist/core/computer/motor/outline.d.ts.map +1 -0
  274. package/dist/core/computer/motor/outline.js +590 -0
  275. package/dist/core/computer/motor/outline.js.map +1 -0
  276. package/dist/core/computer/motor/output.d.ts +18 -0
  277. package/dist/core/computer/motor/output.d.ts.map +1 -0
  278. package/dist/core/computer/motor/output.js +148 -0
  279. package/dist/core/computer/motor/output.js.map +1 -0
  280. package/dist/core/computer/motor/permissions.d.ts +41 -0
  281. package/dist/core/computer/motor/permissions.d.ts.map +1 -0
  282. package/dist/core/computer/motor/permissions.js +51 -0
  283. package/dist/core/computer/motor/permissions.js.map +1 -0
  284. package/dist/core/computer/motor/platform/architecture.d.ts +5 -0
  285. package/dist/core/computer/motor/platform/architecture.d.ts.map +1 -0
  286. package/dist/core/computer/motor/platform/architecture.js +21 -0
  287. package/dist/core/computer/motor/platform/architecture.js.map +1 -0
  288. package/dist/core/computer/motor/platform/coerce.d.ts +4 -0
  289. package/dist/core/computer/motor/platform/coerce.d.ts.map +1 -0
  290. package/dist/core/computer/motor/platform/coerce.js +17 -0
  291. package/dist/core/computer/motor/platform/coerce.js.map +1 -0
  292. package/dist/core/computer/motor/platform/index.d.ts +4 -0
  293. package/dist/core/computer/motor/platform/index.d.ts.map +1 -0
  294. package/dist/core/computer/motor/platform/index.js +79 -0
  295. package/dist/core/computer/motor/platform/index.js.map +1 -0
  296. package/dist/core/computer/motor/platform/linux/backend.d.ts +3 -0
  297. package/dist/core/computer/motor/platform/linux/backend.d.ts.map +1 -0
  298. package/dist/core/computer/motor/platform/linux/backend.js +173 -0
  299. package/dist/core/computer/motor/platform/linux/backend.js.map +1 -0
  300. package/dist/core/computer/motor/platform/linux/helper.d.ts +29 -0
  301. package/dist/core/computer/motor/platform/linux/helper.d.ts.map +1 -0
  302. package/dist/core/computer/motor/platform/linux/helper.js +261 -0
  303. package/dist/core/computer/motor/platform/linux/helper.js.map +1 -0
  304. package/dist/core/computer/motor/platform/macos/backend.d.ts +3 -0
  305. package/dist/core/computer/motor/platform/macos/backend.d.ts.map +1 -0
  306. package/dist/core/computer/motor/platform/macos/backend.js +124 -0
  307. package/dist/core/computer/motor/platform/macos/backend.js.map +1 -0
  308. package/dist/core/computer/motor/platform/macos/browser.d.ts +7 -0
  309. package/dist/core/computer/motor/platform/macos/browser.d.ts.map +1 -0
  310. package/dist/core/computer/motor/platform/macos/browser.js +101 -0
  311. package/dist/core/computer/motor/platform/macos/browser.js.map +1 -0
  312. package/dist/core/computer/motor/platform/macos/helper-path.mjs +34 -0
  313. package/dist/core/computer/motor/platform/macos/helper.d.ts +32 -0
  314. package/dist/core/computer/motor/platform/macos/helper.d.ts.map +1 -0
  315. package/dist/core/computer/motor/platform/macos/helper.js +277 -0
  316. package/dist/core/computer/motor/platform/macos/helper.js.map +1 -0
  317. package/dist/core/computer/motor/platform/macos/permissions.d.ts +4 -0
  318. package/dist/core/computer/motor/platform/macos/permissions.d.ts.map +1 -0
  319. package/dist/core/computer/motor/platform/macos/permissions.js +118 -0
  320. package/dist/core/computer/motor/platform/macos/permissions.js.map +1 -0
  321. package/dist/core/computer/motor/platform/types.d.ts +263 -0
  322. package/dist/core/computer/motor/platform/types.d.ts.map +1 -0
  323. package/dist/core/computer/motor/platform/types.js +2 -0
  324. package/dist/core/computer/motor/platform/types.js.map +1 -0
  325. package/dist/core/computer/motor/platform/windows/backend.d.ts +3 -0
  326. package/dist/core/computer/motor/platform/windows/backend.d.ts.map +1 -0
  327. package/dist/core/computer/motor/platform/windows/backend.js +163 -0
  328. package/dist/core/computer/motor/platform/windows/backend.js.map +1 -0
  329. package/dist/core/computer/motor/platform/windows/helper.d.ts +18 -0
  330. package/dist/core/computer/motor/platform/windows/helper.d.ts.map +1 -0
  331. package/dist/core/computer/motor/platform/windows/helper.js +174 -0
  332. package/dist/core/computer/motor/platform/windows/helper.js.map +1 -0
  333. package/dist/core/computer/motor/root-selection.d.ts +6 -0
  334. package/dist/core/computer/motor/root-selection.d.ts.map +1 -0
  335. package/dist/core/computer/motor/root-selection.js +28 -0
  336. package/dist/core/computer/motor/root-selection.js.map +1 -0
  337. package/dist/core/computer/motor/runtime.d.ts +50 -0
  338. package/dist/core/computer/motor/runtime.d.ts.map +1 -0
  339. package/dist/core/computer/motor/runtime.js +119 -0
  340. package/dist/core/computer/motor/runtime.js.map +1 -0
  341. package/dist/core/computer/motor/state.d.ts +68 -0
  342. package/dist/core/computer/motor/state.d.ts.map +1 -0
  343. package/dist/core/computer/motor/state.js +100 -0
  344. package/dist/core/computer/motor/state.js.map +1 -0
  345. package/dist/core/computer/motor/view.d.ts +6 -0
  346. package/dist/core/computer/motor/view.d.ts.map +1 -0
  347. package/dist/core/computer/motor/view.js +185 -0
  348. package/dist/core/computer/motor/view.js.map +1 -0
  349. package/dist/core/computer/motor-ada.d.ts +159 -0
  350. package/dist/core/computer/motor-ada.d.ts.map +1 -0
  351. package/dist/core/computer/motor-ada.js +412 -0
  352. package/dist/core/computer/motor-ada.js.map +1 -0
  353. package/dist/core/computer/politica.d.ts +56 -0
  354. package/dist/core/computer/politica.d.ts.map +1 -0
  355. package/dist/core/computer/politica.js +163 -0
  356. package/dist/core/computer/politica.js.map +1 -0
  357. package/dist/core/computer/tools.d.ts +26 -0
  358. package/dist/core/computer/tools.d.ts.map +1 -0
  359. package/dist/core/computer/tools.js +399 -0
  360. package/dist/core/computer/tools.js.map +1 -0
  361. package/dist/core/context-breakdown.d.ts +80 -0
  362. package/dist/core/context-breakdown.d.ts.map +1 -0
  363. package/dist/core/context-breakdown.js +87 -0
  364. package/dist/core/context-breakdown.js.map +1 -0
  365. package/dist/core/credenciales/cliente.d.ts +60 -0
  366. package/dist/core/credenciales/cliente.d.ts.map +1 -0
  367. package/dist/core/credenciales/cliente.js +81 -0
  368. package/dist/core/credenciales/cliente.js.map +1 -0
  369. package/dist/core/credenciales/pool.d.ts +96 -0
  370. package/dist/core/credenciales/pool.d.ts.map +1 -0
  371. package/dist/core/credenciales/pool.js +128 -0
  372. package/dist/core/credenciales/pool.js.map +1 -0
  373. package/dist/core/credenciales/reposo.d.ts +93 -0
  374. package/dist/core/credenciales/reposo.d.ts.map +1 -0
  375. package/dist/core/credenciales/reposo.js +489 -0
  376. package/dist/core/credenciales/reposo.js.map +1 -0
  377. package/dist/core/credenciales/servidor.d.ts +55 -0
  378. package/dist/core/credenciales/servidor.d.ts.map +1 -0
  379. package/dist/core/credenciales/servidor.js +205 -0
  380. package/dist/core/credenciales/servidor.js.map +1 -0
  381. package/dist/core/descubrimiento/comandos.d.ts +30 -0
  382. package/dist/core/descubrimiento/comandos.d.ts.map +1 -0
  383. package/dist/core/descubrimiento/comandos.js +76 -0
  384. package/dist/core/descubrimiento/comandos.js.map +1 -0
  385. package/dist/core/descubrimiento/mcp.d.ts +42 -0
  386. package/dist/core/descubrimiento/mcp.d.ts.map +1 -0
  387. package/dist/core/descubrimiento/mcp.js +130 -0
  388. package/dist/core/descubrimiento/mcp.js.map +1 -0
  389. package/dist/core/descubrimiento/reglas.d.ts +61 -0
  390. package/dist/core/descubrimiento/reglas.d.ts.map +1 -0
  391. package/dist/core/descubrimiento/reglas.js +134 -0
  392. package/dist/core/descubrimiento/reglas.js.map +1 -0
  393. package/dist/core/descubrimiento/skills.d.ts +57 -0
  394. package/dist/core/descubrimiento/skills.d.ts.map +1 -0
  395. package/dist/core/descubrimiento/skills.js +113 -0
  396. package/dist/core/descubrimiento/skills.js.map +1 -0
  397. package/dist/core/dificultad/clasificador.d.ts +133 -0
  398. package/dist/core/dificultad/clasificador.d.ts.map +1 -0
  399. package/dist/core/dificultad/clasificador.js +288 -0
  400. package/dist/core/dificultad/clasificador.js.map +1 -0
  401. package/dist/core/edicion/diario.d.ts +37 -0
  402. package/dist/core/edicion/diario.d.ts.map +1 -0
  403. package/dist/core/edicion/diario.js +51 -0
  404. package/dist/core/edicion/diario.js.map +1 -0
  405. package/dist/core/edicion/hashline.d.ts +71 -0
  406. package/dist/core/edicion/hashline.d.ts.map +1 -0
  407. package/dist/core/edicion/hashline.js +111 -0
  408. package/dist/core/edicion/hashline.js.map +1 -0
  409. package/dist/core/edicion/hueco.d.ts +64 -0
  410. package/dist/core/edicion/hueco.d.ts.map +1 -0
  411. package/dist/core/edicion/hueco.js +162 -0
  412. package/dist/core/edicion/hueco.js.map +1 -0
  413. package/dist/core/edicion/limpiar-carga.d.ts +39 -0
  414. package/dist/core/edicion/limpiar-carga.d.ts.map +1 -0
  415. package/dist/core/edicion/limpiar-carga.js +121 -0
  416. package/dist/core/edicion/limpiar-carga.js.map +1 -0
  417. package/dist/core/edicion/observador.d.ts +80 -0
  418. package/dist/core/edicion/observador.d.ts.map +1 -0
  419. package/dist/core/edicion/observador.js +183 -0
  420. package/dist/core/edicion/observador.js.map +1 -0
  421. package/dist/core/edicion/parecido.d.ts +53 -0
  422. package/dist/core/edicion/parecido.d.ts.map +1 -0
  423. package/dist/core/edicion/parecido.js +145 -0
  424. package/dist/core/edicion/parecido.js.map +1 -0
  425. package/dist/core/edicion/parseo.d.ts +58 -0
  426. package/dist/core/edicion/parseo.d.ts.map +1 -0
  427. package/dist/core/edicion/parseo.js +124 -0
  428. package/dist/core/edicion/parseo.js.map +1 -0
  429. package/dist/core/edicion/reparacion.d.ts +67 -0
  430. package/dist/core/edicion/reparacion.d.ts.map +1 -0
  431. package/dist/core/edicion/reparacion.js +202 -0
  432. package/dist/core/edicion/reparacion.js.map +1 -0
  433. package/dist/core/edicion/sangria.d.ts +121 -0
  434. package/dist/core/edicion/sangria.d.ts.map +1 -0
  435. package/dist/core/edicion/sangria.js +341 -0
  436. package/dist/core/edicion/sangria.js.map +1 -0
  437. package/dist/core/edicion/tipos.d.ts +58 -0
  438. package/dist/core/edicion/tipos.d.ts.map +1 -0
  439. package/dist/core/edicion/tipos.js +160 -0
  440. package/dist/core/edicion/tipos.js.map +1 -0
  441. package/dist/core/errores/proveedor.d.ts +53 -0
  442. package/dist/core/errores/proveedor.d.ts.map +1 -0
  443. package/dist/core/errores/proveedor.js +191 -0
  444. package/dist/core/errores/proveedor.js.map +1 -0
  445. package/dist/core/estructural/buscar.d.ts +61 -0
  446. package/dist/core/estructural/buscar.d.ts.map +1 -0
  447. package/dist/core/estructural/buscar.js +261 -0
  448. package/dist/core/estructural/buscar.js.map +1 -0
  449. package/dist/core/estructural/editar.d.ts +36 -0
  450. package/dist/core/estructural/editar.d.ts.map +1 -0
  451. package/dist/core/estructural/editar.js +123 -0
  452. package/dist/core/estructural/editar.js.map +1 -0
  453. package/dist/core/estructural/tools.d.ts +11 -0
  454. package/dist/core/estructural/tools.d.ts.map +1 -0
  455. package/dist/core/estructural/tools.js +100 -0
  456. package/dist/core/estructural/tools.js.map +1 -0
  457. package/dist/core/experimental.d.ts.map +1 -1
  458. package/dist/core/experimental.js +10 -1
  459. package/dist/core/experimental.js.map +1 -1
  460. package/dist/core/extensiones/suplantacion.d.ts +49 -0
  461. package/dist/core/extensiones/suplantacion.d.ts.map +1 -0
  462. package/dist/core/extensiones/suplantacion.js +61 -0
  463. package/dist/core/extensiones/suplantacion.js.map +1 -0
  464. package/dist/core/extensiones/vigilancia.d.ts +54 -0
  465. package/dist/core/extensiones/vigilancia.d.ts.map +1 -0
  466. package/dist/core/extensiones/vigilancia.js +217 -0
  467. package/dist/core/extensiones/vigilancia.js.map +1 -0
  468. package/dist/core/extensions/loader.d.ts.map +1 -1
  469. package/dist/core/extensions/loader.js +11 -1
  470. package/dist/core/extensions/loader.js.map +1 -1
  471. package/dist/core/harness/fuentes-de-contexto.d.ts +29 -0
  472. package/dist/core/harness/fuentes-de-contexto.d.ts.map +1 -0
  473. package/dist/core/harness/fuentes-de-contexto.js +44 -0
  474. package/dist/core/harness/fuentes-de-contexto.js.map +1 -0
  475. package/dist/core/harness/refine.d.ts +40 -0
  476. package/dist/core/harness/refine.d.ts.map +1 -0
  477. package/dist/core/harness/refine.js +109 -0
  478. package/dist/core/harness/refine.js.map +1 -0
  479. package/dist/core/harness/store.d.ts +75 -0
  480. package/dist/core/harness/store.d.ts.map +1 -0
  481. package/dist/core/harness/store.js +149 -0
  482. package/dist/core/harness/store.js.map +1 -0
  483. package/dist/core/hooks/contract.d.ts +85 -0
  484. package/dist/core/hooks/contract.d.ts.map +1 -0
  485. package/dist/core/hooks/contract.js +140 -0
  486. package/dist/core/hooks/contract.js.map +1 -0
  487. package/dist/core/hooks/runner.d.ts +42 -0
  488. package/dist/core/hooks/runner.d.ts.map +1 -0
  489. package/dist/core/hooks/runner.js +85 -0
  490. package/dist/core/hooks/runner.js.map +1 -0
  491. package/dist/core/hooks/session-hooks.d.ts +62 -0
  492. package/dist/core/hooks/session-hooks.d.ts.map +1 -0
  493. package/dist/core/hooks/session-hooks.js +84 -0
  494. package/dist/core/hooks/session-hooks.js.map +1 -0
  495. package/dist/core/importar/formatos.d.ts +69 -0
  496. package/dist/core/importar/formatos.d.ts.map +1 -0
  497. package/dist/core/importar/formatos.js +235 -0
  498. package/dist/core/importar/formatos.js.map +1 -0
  499. package/dist/core/interactivo/deteccion.d.ts +44 -0
  500. package/dist/core/interactivo/deteccion.d.ts.map +1 -0
  501. package/dist/core/interactivo/deteccion.js +82 -0
  502. package/dist/core/interactivo/deteccion.js.map +1 -0
  503. package/dist/core/interactivo/sesion.d.ts +50 -0
  504. package/dist/core/interactivo/sesion.d.ts.map +1 -0
  505. package/dist/core/interactivo/sesion.js +156 -0
  506. package/dist/core/interactivo/sesion.js.map +1 -0
  507. package/dist/core/interactivo/tools.d.ts +21 -0
  508. package/dist/core/interactivo/tools.d.ts.map +1 -0
  509. package/dist/core/interactivo/tools.js +122 -0
  510. package/dist/core/interactivo/tools.js.map +1 -0
  511. package/dist/core/lectura/mapa.d.ts +70 -0
  512. package/dist/core/lectura/mapa.d.ts.map +1 -0
  513. package/dist/core/lectura/mapa.js +177 -0
  514. package/dist/core/lectura/mapa.js.map +1 -0
  515. package/dist/core/lectura/pdf.d.ts +49 -0
  516. package/dist/core/lectura/pdf.d.ts.map +1 -0
  517. package/dist/core/lectura/pdf.js +337 -0
  518. package/dist/core/lectura/pdf.js.map +1 -0
  519. package/dist/core/limpieza/comprobadores.d.ts +28 -0
  520. package/dist/core/limpieza/comprobadores.d.ts.map +1 -0
  521. package/dist/core/limpieza/comprobadores.js +311 -0
  522. package/dist/core/limpieza/comprobadores.js.map +1 -0
  523. package/dist/core/limpieza/parseo.d.ts +19 -0
  524. package/dist/core/limpieza/parseo.d.ts.map +1 -0
  525. package/dist/core/limpieza/parseo.js +124 -0
  526. package/dist/core/limpieza/parseo.js.map +1 -0
  527. package/dist/core/limpieza/reparacion.d.ts +77 -0
  528. package/dist/core/limpieza/reparacion.d.ts.map +1 -0
  529. package/dist/core/limpieza/reparacion.js +254 -0
  530. package/dist/core/limpieza/reparacion.js.map +1 -0
  531. package/dist/core/limpieza/tipos.d.ts +56 -0
  532. package/dist/core/limpieza/tipos.d.ts.map +1 -0
  533. package/dist/core/limpieza/tipos.js +9 -0
  534. package/dist/core/limpieza/tipos.js.map +1 -0
  535. package/dist/core/lsp/cliente.d.ts +52 -0
  536. package/dist/core/lsp/cliente.d.ts.map +1 -0
  537. package/dist/core/lsp/cliente.js +232 -0
  538. package/dist/core/lsp/cliente.js.map +1 -0
  539. package/dist/core/lsp/descubrimiento.d.ts +62 -0
  540. package/dist/core/lsp/descubrimiento.d.ts.map +1 -0
  541. package/dist/core/lsp/descubrimiento.js +152 -0
  542. package/dist/core/lsp/descubrimiento.js.map +1 -0
  543. package/dist/core/lsp/ediciones.d.ts +59 -0
  544. package/dist/core/lsp/ediciones.d.ts.map +1 -0
  545. package/dist/core/lsp/ediciones.js +89 -0
  546. package/dist/core/lsp/ediciones.js.map +1 -0
  547. package/dist/core/lsp/extension.d.ts +28 -0
  548. package/dist/core/lsp/extension.d.ts.map +1 -0
  549. package/dist/core/lsp/extension.js +34 -0
  550. package/dist/core/lsp/extension.js.map +1 -0
  551. package/dist/core/lsp/tools.d.ts +45 -0
  552. package/dist/core/lsp/tools.d.ts.map +1 -0
  553. package/dist/core/lsp/tools.js +265 -0
  554. package/dist/core/lsp/tools.js.map +1 -0
  555. package/dist/core/mcp/client.d.ts +60 -0
  556. package/dist/core/mcp/client.d.ts.map +1 -0
  557. package/dist/core/mcp/client.js +139 -0
  558. package/dist/core/mcp/client.js.map +1 -0
  559. package/dist/core/mcp/manager.d.ts +86 -0
  560. package/dist/core/mcp/manager.d.ts.map +1 -0
  561. package/dist/core/mcp/manager.js +124 -0
  562. package/dist/core/mcp/manager.js.map +1 -0
  563. package/dist/core/mcp/tools.d.ts +15 -0
  564. package/dist/core/mcp/tools.d.ts.map +1 -0
  565. package/dist/core/mcp/tools.js +84 -0
  566. package/dist/core/mcp/tools.js.map +1 -0
  567. package/dist/core/memory-engine/audit-export.d.ts +12 -10
  568. package/dist/core/memory-engine/audit-export.d.ts.map +1 -1
  569. package/dist/core/memory-engine/audit-export.js +15 -0
  570. package/dist/core/memory-engine/audit-export.js.map +1 -1
  571. package/dist/core/memory-engine/audit-indice.d.ts +153 -0
  572. package/dist/core/memory-engine/audit-indice.d.ts.map +1 -0
  573. package/dist/core/memory-engine/audit-indice.js +271 -0
  574. package/dist/core/memory-engine/audit-indice.js.map +1 -0
  575. package/dist/core/memory-engine/audit.d.ts +18 -6
  576. package/dist/core/memory-engine/audit.d.ts.map +1 -1
  577. package/dist/core/memory-engine/audit.js +25 -42
  578. package/dist/core/memory-engine/audit.js.map +1 -1
  579. package/dist/core/memory-engine/engine.d.ts +19 -1
  580. package/dist/core/memory-engine/engine.d.ts.map +1 -1
  581. package/dist/core/memory-engine/engine.js +71 -5
  582. package/dist/core/memory-engine/engine.js.map +1 -1
  583. package/dist/core/memory-engine/observation-store.d.ts +0 -2
  584. package/dist/core/memory-engine/observation-store.d.ts.map +1 -1
  585. package/dist/core/memory-engine/observation-store.js +4 -5
  586. package/dist/core/memory-engine/observation-store.js.map +1 -1
  587. package/dist/core/memory-engine/refinements.d.ts +51 -0
  588. package/dist/core/memory-engine/refinements.d.ts.map +1 -0
  589. package/dist/core/memory-engine/refinements.js +99 -0
  590. package/dist/core/memory-engine/refinements.js.map +1 -0
  591. package/dist/core/memory-engine/tools.d.ts.map +1 -1
  592. package/dist/core/memory-engine/tools.js +27 -1
  593. package/dist/core/memory-engine/tools.js.map +1 -1
  594. package/dist/core/memory-engine/types.d.ts +2 -0
  595. package/dist/core/memory-engine/types.d.ts.map +1 -1
  596. package/dist/core/memory-engine/types.js.map +1 -1
  597. package/dist/core/model-resolver.d.ts.map +1 -1
  598. package/dist/core/model-resolver.js +7 -1
  599. package/dist/core/model-resolver.js.map +1 -1
  600. package/dist/core/muestreo-restringido.d.ts +49 -0
  601. package/dist/core/muestreo-restringido.d.ts.map +1 -0
  602. package/dist/core/muestreo-restringido.js +65 -0
  603. package/dist/core/muestreo-restringido.js.map +1 -0
  604. package/dist/core/orphan-process-journal.d.ts +48 -0
  605. package/dist/core/orphan-process-journal.d.ts.map +1 -0
  606. package/dist/core/orphan-process-journal.js +116 -0
  607. package/dist/core/orphan-process-journal.js.map +1 -0
  608. package/dist/core/privacidad/categorias-especiales.d.ts +186 -0
  609. package/dist/core/privacidad/categorias-especiales.d.ts.map +1 -0
  610. package/dist/core/privacidad/categorias-especiales.js +745 -0
  611. package/dist/core/privacidad/categorias-especiales.js.map +1 -0
  612. package/dist/core/privacidad/tapar.d.ts +48 -0
  613. package/dist/core/privacidad/tapar.d.ts.map +1 -0
  614. package/dist/core/privacidad/tapar.js +66 -0
  615. package/dist/core/privacidad/tapar.js.map +1 -0
  616. package/dist/core/prompt-cache/diagnostics.d.ts +33 -0
  617. package/dist/core/prompt-cache/diagnostics.d.ts.map +1 -0
  618. package/dist/core/prompt-cache/diagnostics.js +100 -0
  619. package/dist/core/prompt-cache/diagnostics.js.map +1 -0
  620. package/dist/core/prompt-cache/index.d.ts +33 -0
  621. package/dist/core/prompt-cache/index.d.ts.map +1 -0
  622. package/dist/core/prompt-cache/index.js +189 -0
  623. package/dist/core/prompt-cache/index.js.map +1 -0
  624. package/dist/core/prompt-cache/payload.d.ts +58 -0
  625. package/dist/core/prompt-cache/payload.d.ts.map +1 -0
  626. package/dist/core/prompt-cache/payload.js +122 -0
  627. package/dist/core/prompt-cache/payload.js.map +1 -0
  628. package/dist/core/prompt-cache/stabilize.d.ts +65 -0
  629. package/dist/core/prompt-cache/stabilize.d.ts.map +1 -0
  630. package/dist/core/prompt-cache/stabilize.js +137 -0
  631. package/dist/core/prompt-cache/stabilize.js.map +1 -0
  632. package/dist/core/prompt-cache/stats.d.ts +55 -0
  633. package/dist/core/prompt-cache/stats.d.ts.map +1 -0
  634. package/dist/core/prompt-cache/stats.js +158 -0
  635. package/dist/core/prompt-cache/stats.js.map +1 -0
  636. package/dist/core/prompt-eleccion.d.ts +38 -0
  637. package/dist/core/prompt-eleccion.d.ts.map +1 -0
  638. package/dist/core/prompt-eleccion.js +46 -0
  639. package/dist/core/prompt-eleccion.js.map +1 -0
  640. package/dist/core/prompts/montar.d.ts +75 -0
  641. package/dist/core/prompts/montar.d.ts.map +1 -0
  642. package/dist/core/prompts/montar.js +146 -0
  643. package/dist/core/prompts/montar.js.map +1 -0
  644. package/dist/core/prompts/plantilla.d.ts +54 -0
  645. package/dist/core/prompts/plantilla.d.ts.map +1 -0
  646. package/dist/core/prompts/plantilla.js +153 -0
  647. package/dist/core/prompts/plantilla.js.map +1 -0
  648. package/dist/core/prompts/sistema.md +134 -0
  649. package/dist/core/provider-attribution.d.ts +24 -2
  650. package/dist/core/provider-attribution.d.ts.map +1 -1
  651. package/dist/core/provider-attribution.js +24 -42
  652. package/dist/core/provider-attribution.js.map +1 -1
  653. package/dist/core/remoto/destino.d.ts +38 -0
  654. package/dist/core/remoto/destino.d.ts.map +1 -0
  655. package/dist/core/remoto/destino.js +105 -0
  656. package/dist/core/remoto/destino.js.map +1 -0
  657. package/dist/core/remoto/ejecutor.d.ts +59 -0
  658. package/dist/core/remoto/ejecutor.d.ts.map +1 -0
  659. package/dist/core/remoto/ejecutor.js +225 -0
  660. package/dist/core/remoto/ejecutor.js.map +1 -0
  661. package/dist/core/remoto/ficheros.d.ts +29 -0
  662. package/dist/core/remoto/ficheros.d.ts.map +1 -0
  663. package/dist/core/remoto/ficheros.js +90 -0
  664. package/dist/core/remoto/ficheros.js.map +1 -0
  665. package/dist/core/remoto/registro-extension.d.ts +15 -0
  666. package/dist/core/remoto/registro-extension.d.ts.map +1 -0
  667. package/dist/core/remoto/registro-extension.js +17 -0
  668. package/dist/core/remoto/registro-extension.js.map +1 -0
  669. package/dist/core/remoto/tools.d.ts +28 -0
  670. package/dist/core/remoto/tools.d.ts.map +1 -0
  671. package/dist/core/remoto/tools.js +31 -0
  672. package/dist/core/remoto/tools.js.map +1 -0
  673. package/dist/core/resource-loader.d.ts +8 -0
  674. package/dist/core/resource-loader.d.ts.map +1 -1
  675. package/dist/core/resource-loader.js +93 -1
  676. package/dist/core/resource-loader.js.map +1 -1
  677. package/dist/core/revision/hallazgo.d.ts +51 -0
  678. package/dist/core/revision/hallazgo.d.ts.map +1 -0
  679. package/dist/core/revision/hallazgo.js +73 -0
  680. package/dist/core/revision/hallazgo.js.map +1 -0
  681. package/dist/core/revision/revisor.d.ts +81 -0
  682. package/dist/core/revision/revisor.d.ts.map +1 -0
  683. package/dist/core/revision/revisor.js +132 -0
  684. package/dist/core/revision/revisor.js.map +1 -0
  685. package/dist/core/revision/sarif.d.ts +30 -0
  686. package/dist/core/revision/sarif.d.ts.map +1 -0
  687. package/dist/core/revision/sarif.js +83 -0
  688. package/dist/core/revision/sarif.js.map +1 -0
  689. package/dist/core/scheduler/scheduler.d.ts +87 -0
  690. package/dist/core/scheduler/scheduler.d.ts.map +1 -0
  691. package/dist/core/scheduler/scheduler.js +178 -0
  692. package/dist/core/scheduler/scheduler.js.map +1 -0
  693. package/dist/core/sesion/cerrar-llamadas-abiertas.d.ts +49 -0
  694. package/dist/core/sesion/cerrar-llamadas-abiertas.d.ts.map +1 -0
  695. package/dist/core/sesion/cerrar-llamadas-abiertas.js +77 -0
  696. package/dist/core/sesion/cerrar-llamadas-abiertas.js.map +1 -0
  697. package/dist/core/settings-manager.d.ts +110 -6
  698. package/dist/core/settings-manager.d.ts.map +1 -1
  699. package/dist/core/settings-manager.js +53 -2
  700. package/dist/core/settings-manager.js.map +1 -1
  701. package/dist/core/slash-commands.d.ts.map +1 -1
  702. package/dist/core/slash-commands.js +3 -3
  703. package/dist/core/slash-commands.js.map +1 -1
  704. package/dist/core/system-prompt.d.ts +17 -0
  705. package/dist/core/system-prompt.d.ts.map +1 -1
  706. package/dist/core/system-prompt.js +13 -5
  707. package/dist/core/system-prompt.js.map +1 -1
  708. package/dist/core/telemetria/otlp.d.ts +122 -0
  709. package/dist/core/telemetria/otlp.d.ts.map +1 -0
  710. package/dist/core/telemetria/otlp.js +373 -0
  711. package/dist/core/telemetria/otlp.js.map +1 -0
  712. package/dist/core/tools/coercion-argumentos.d.ts +48 -0
  713. package/dist/core/tools/coercion-argumentos.d.ts.map +1 -0
  714. package/dist/core/tools/coercion-argumentos.js +99 -0
  715. package/dist/core/tools/coercion-argumentos.js.map +1 -0
  716. package/dist/core/tools/edit-diff.d.ts +0 -7
  717. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  718. package/dist/core/tools/edit-diff.js +232 -7
  719. package/dist/core/tools/edit-diff.js.map +1 -1
  720. package/dist/core/tools/edit.d.ts.map +1 -1
  721. package/dist/core/tools/edit.js +8 -1
  722. package/dist/core/tools/edit.js.map +1 -1
  723. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  724. package/dist/core/tools/file-mutation-queue.js +5 -0
  725. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  726. package/dist/core/tools/json-tolerante.d.ts +30 -0
  727. package/dist/core/tools/json-tolerante.d.ts.map +1 -0
  728. package/dist/core/tools/json-tolerante.js +70 -0
  729. package/dist/core/tools/json-tolerante.js.map +1 -0
  730. package/dist/core/tools/read.d.ts.map +1 -1
  731. package/dist/core/tools/read.js +17 -0
  732. package/dist/core/tools/read.js.map +1 -1
  733. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -1
  734. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  735. package/dist/core/tools/tool-definition-wrapper.js +17 -1
  736. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  737. package/dist/core/turno-vacio/detectar.d.ts +38 -0
  738. package/dist/core/turno-vacio/detectar.d.ts.map +1 -0
  739. package/dist/core/turno-vacio/detectar.js +66 -0
  740. package/dist/core/turno-vacio/detectar.js.map +1 -0
  741. package/dist/core/undo/journal.d.ts +57 -0
  742. package/dist/core/undo/journal.d.ts.map +1 -0
  743. package/dist/core/undo/journal.js +178 -0
  744. package/dist/core/undo/journal.js.map +1 -0
  745. package/dist/core/urls-internas/auditoria.d.ts +10 -0
  746. package/dist/core/urls-internas/auditoria.d.ts.map +1 -0
  747. package/dist/core/urls-internas/auditoria.js +42 -0
  748. package/dist/core/urls-internas/auditoria.js.map +1 -0
  749. package/dist/core/urls-internas/boveda.d.ts +13 -0
  750. package/dist/core/urls-internas/boveda.d.ts.map +1 -0
  751. package/dist/core/urls-internas/boveda.js +40 -0
  752. package/dist/core/urls-internas/boveda.js.map +1 -0
  753. package/dist/core/urls-internas/lector.d.ts +30 -0
  754. package/dist/core/urls-internas/lector.d.ts.map +1 -0
  755. package/dist/core/urls-internas/lector.js +56 -0
  756. package/dist/core/urls-internas/lector.js.map +1 -0
  757. package/dist/core/urls-internas/memoria.d.ts +11 -0
  758. package/dist/core/urls-internas/memoria.d.ts.map +1 -0
  759. package/dist/core/urls-internas/memoria.js +25 -0
  760. package/dist/core/urls-internas/memoria.js.map +1 -0
  761. package/dist/core/urls-internas/parse.d.ts +28 -0
  762. package/dist/core/urls-internas/parse.d.ts.map +1 -0
  763. package/dist/core/urls-internas/parse.js +32 -0
  764. package/dist/core/urls-internas/parse.js.map +1 -0
  765. package/dist/core/urls-internas/reglas.d.ts +10 -0
  766. package/dist/core/urls-internas/reglas.d.ts.map +1 -0
  767. package/dist/core/urls-internas/reglas.js +31 -0
  768. package/dist/core/urls-internas/reglas.js.map +1 -0
  769. package/dist/core/usage/aggregate.d.ts +68 -0
  770. package/dist/core/usage/aggregate.d.ts.map +1 -0
  771. package/dist/core/usage/aggregate.js +93 -0
  772. package/dist/core/usage/aggregate.js.map +1 -0
  773. package/dist/core/usage/cuota/anthropic.d.ts +10 -0
  774. package/dist/core/usage/cuota/anthropic.d.ts.map +1 -0
  775. package/dist/core/usage/cuota/anthropic.js +63 -0
  776. package/dist/core/usage/cuota/anthropic.js.map +1 -0
  777. package/dist/core/usage/cuota/cache.d.ts +20 -0
  778. package/dist/core/usage/cuota/cache.d.ts.map +1 -0
  779. package/dist/core/usage/cuota/cache.js +36 -0
  780. package/dist/core/usage/cuota/cache.js.map +1 -0
  781. package/dist/core/usage/cuota/github-copilot.d.ts +9 -0
  782. package/dist/core/usage/cuota/github-copilot.d.ts.map +1 -0
  783. package/dist/core/usage/cuota/github-copilot.js +67 -0
  784. package/dist/core/usage/cuota/github-copilot.js.map +1 -0
  785. package/dist/core/usage/cuota/google-gemini.d.ts +9 -0
  786. package/dist/core/usage/cuota/google-gemini.d.ts.map +1 -0
  787. package/dist/core/usage/cuota/google-gemini.js +72 -0
  788. package/dist/core/usage/cuota/google-gemini.js.map +1 -0
  789. package/dist/core/usage/cuota/index.d.ts +25 -0
  790. package/dist/core/usage/cuota/index.d.ts.map +1 -0
  791. package/dist/core/usage/cuota/index.js +50 -0
  792. package/dist/core/usage/cuota/index.js.map +1 -0
  793. package/dist/core/usage/cuota/openai-codex.d.ts +9 -0
  794. package/dist/core/usage/cuota/openai-codex.d.ts.map +1 -0
  795. package/dist/core/usage/cuota/openai-codex.js +59 -0
  796. package/dist/core/usage/cuota/openai-codex.js.map +1 -0
  797. package/dist/core/usage/cuota/openrouter.d.ts +10 -0
  798. package/dist/core/usage/cuota/openrouter.d.ts.map +1 -0
  799. package/dist/core/usage/cuota/openrouter.js +47 -0
  800. package/dist/core/usage/cuota/openrouter.js.map +1 -0
  801. package/dist/core/usage/cuota/tipos.d.ts +50 -0
  802. package/dist/core/usage/cuota/tipos.d.ts.map +1 -0
  803. package/dist/core/usage/cuota/tipos.js +25 -0
  804. package/dist/core/usage/cuota/tipos.js.map +1 -0
  805. package/dist/core/web/busqueda-nativa.d.ts +82 -0
  806. package/dist/core/web/busqueda-nativa.d.ts.map +1 -0
  807. package/dist/core/web/busqueda-nativa.js +163 -0
  808. package/dist/core/web/busqueda-nativa.js.map +1 -0
  809. package/dist/core/web/destino.d.ts +58 -0
  810. package/dist/core/web/destino.d.ts.map +1 -0
  811. package/dist/core/web/destino.js +127 -0
  812. package/dist/core/web/destino.js.map +1 -0
  813. package/dist/core/web/proveedores/brave.d.ts +6 -0
  814. package/dist/core/web/proveedores/brave.d.ts.map +1 -0
  815. package/dist/core/web/proveedores/brave.js +20 -0
  816. package/dist/core/web/proveedores/brave.js.map +1 -0
  817. package/dist/core/web/proveedores/claves.d.ts +19 -0
  818. package/dist/core/web/proveedores/claves.d.ts.map +1 -0
  819. package/dist/core/web/proveedores/claves.js +36 -0
  820. package/dist/core/web/proveedores/claves.js.map +1 -0
  821. package/dist/core/web/proveedores/duckduckgo.d.ts +3 -0
  822. package/dist/core/web/proveedores/duckduckgo.d.ts.map +1 -0
  823. package/dist/core/web/proveedores/duckduckgo.js +109 -0
  824. package/dist/core/web/proveedores/duckduckgo.js.map +1 -0
  825. package/dist/core/web/proveedores/ecosia.d.ts +3 -0
  826. package/dist/core/web/proveedores/ecosia.d.ts.map +1 -0
  827. package/dist/core/web/proveedores/ecosia.js +72 -0
  828. package/dist/core/web/proveedores/ecosia.js.map +1 -0
  829. package/dist/core/web/proveedores/exa.d.ts +6 -0
  830. package/dist/core/web/proveedores/exa.d.ts.map +1 -0
  831. package/dist/core/web/proveedores/exa.js +24 -0
  832. package/dist/core/web/proveedores/exa.js.map +1 -0
  833. package/dist/core/web/proveedores/mojeek.d.ts +3 -0
  834. package/dist/core/web/proveedores/mojeek.d.ts.map +1 -0
  835. package/dist/core/web/proveedores/mojeek.js +65 -0
  836. package/dist/core/web/proveedores/mojeek.js.map +1 -0
  837. package/dist/core/web/proveedores/navegador.d.ts +10 -0
  838. package/dist/core/web/proveedores/navegador.d.ts.map +1 -0
  839. package/dist/core/web/proveedores/navegador.js +13 -0
  840. package/dist/core/web/proveedores/navegador.js.map +1 -0
  841. package/dist/core/web/proveedores/registro.d.ts +43 -0
  842. package/dist/core/web/proveedores/registro.d.ts.map +1 -0
  843. package/dist/core/web/proveedores/registro.js +49 -0
  844. package/dist/core/web/proveedores/registro.js.map +1 -0
  845. package/dist/core/web/proveedores/searxng.d.ts +9 -0
  846. package/dist/core/web/proveedores/searxng.d.ts.map +1 -0
  847. package/dist/core/web/proveedores/searxng.js +17 -0
  848. package/dist/core/web/proveedores/searxng.js.map +1 -0
  849. package/dist/core/web/proveedores/serper.d.ts +9 -0
  850. package/dist/core/web/proveedores/serper.d.ts.map +1 -0
  851. package/dist/core/web/proveedores/serper.js +19 -0
  852. package/dist/core/web/proveedores/serper.js.map +1 -0
  853. package/dist/core/web/proveedores/tavily.d.ts +6 -0
  854. package/dist/core/web/proveedores/tavily.d.ts.map +1 -0
  855. package/dist/core/web/proveedores/tavily.js +19 -0
  856. package/dist/core/web/proveedores/tavily.js.map +1 -0
  857. package/dist/core/web/proveedores/tipos.d.ts +36 -0
  858. package/dist/core/web/proveedores/tipos.d.ts.map +1 -0
  859. package/dist/core/web/proveedores/tipos.js +10 -0
  860. package/dist/core/web/proveedores/tipos.js.map +1 -0
  861. package/dist/core/web/registro-red.d.ts +43 -0
  862. package/dist/core/web/registro-red.d.ts.map +1 -0
  863. package/dist/core/web/registro-red.js +86 -0
  864. package/dist/core/web/registro-red.js.map +1 -0
  865. package/dist/core/web/scrapers/cisa-kev.d.ts +10 -0
  866. package/dist/core/web/scrapers/cisa-kev.d.ts.map +1 -0
  867. package/dist/core/web/scrapers/cisa-kev.js +38 -0
  868. package/dist/core/web/scrapers/cisa-kev.js.map +1 -0
  869. package/dist/core/web/scrapers/crates-io.d.ts +11 -0
  870. package/dist/core/web/scrapers/crates-io.d.ts.map +1 -0
  871. package/dist/core/web/scrapers/crates-io.js +29 -0
  872. package/dist/core/web/scrapers/crates-io.js.map +1 -0
  873. package/dist/core/web/scrapers/docs-rs.d.ts +10 -0
  874. package/dist/core/web/scrapers/docs-rs.d.ts.map +1 -0
  875. package/dist/core/web/scrapers/docs-rs.js +35 -0
  876. package/dist/core/web/scrapers/docs-rs.js.map +1 -0
  877. package/dist/core/web/scrapers/github.d.ts +3 -0
  878. package/dist/core/web/scrapers/github.d.ts.map +1 -0
  879. package/dist/core/web/scrapers/github.js +72 -0
  880. package/dist/core/web/scrapers/github.js.map +1 -0
  881. package/dist/core/web/scrapers/go-pkg.d.ts +7 -0
  882. package/dist/core/web/scrapers/go-pkg.d.ts.map +1 -0
  883. package/dist/core/web/scrapers/go-pkg.js +30 -0
  884. package/dist/core/web/scrapers/go-pkg.js.map +1 -0
  885. package/dist/core/web/scrapers/hex.d.ts +6 -0
  886. package/dist/core/web/scrapers/hex.d.ts.map +1 -0
  887. package/dist/core/web/scrapers/hex.js +27 -0
  888. package/dist/core/web/scrapers/hex.js.map +1 -0
  889. package/dist/core/web/scrapers/maven.d.ts +8 -0
  890. package/dist/core/web/scrapers/maven.d.ts.map +1 -0
  891. package/dist/core/web/scrapers/maven.js +41 -0
  892. package/dist/core/web/scrapers/maven.js.map +1 -0
  893. package/dist/core/web/scrapers/mdn.d.ts +6 -0
  894. package/dist/core/web/scrapers/mdn.d.ts.map +1 -0
  895. package/dist/core/web/scrapers/mdn.js +28 -0
  896. package/dist/core/web/scrapers/mdn.js.map +1 -0
  897. package/dist/core/web/scrapers/npm.d.ts +11 -0
  898. package/dist/core/web/scrapers/npm.d.ts.map +1 -0
  899. package/dist/core/web/scrapers/npm.js +57 -0
  900. package/dist/core/web/scrapers/npm.js.map +1 -0
  901. package/dist/core/web/scrapers/nuget.d.ts +8 -0
  902. package/dist/core/web/scrapers/nuget.d.ts.map +1 -0
  903. package/dist/core/web/scrapers/nuget.js +22 -0
  904. package/dist/core/web/scrapers/nuget.js.map +1 -0
  905. package/dist/core/web/scrapers/nvd.d.ts +7 -0
  906. package/dist/core/web/scrapers/nvd.d.ts.map +1 -0
  907. package/dist/core/web/scrapers/nvd.js +37 -0
  908. package/dist/core/web/scrapers/nvd.js.map +1 -0
  909. package/dist/core/web/scrapers/pypi.d.ts +6 -0
  910. package/dist/core/web/scrapers/pypi.d.ts.map +1 -0
  911. package/dist/core/web/scrapers/pypi.js +31 -0
  912. package/dist/core/web/scrapers/pypi.js.map +1 -0
  913. package/dist/core/web/scrapers/registro.d.ts +13 -0
  914. package/dist/core/web/scrapers/registro.d.ts.map +1 -0
  915. package/dist/core/web/scrapers/registro.js +65 -0
  916. package/dist/core/web/scrapers/registro.js.map +1 -0
  917. package/dist/core/web/scrapers/rubygems.d.ts +6 -0
  918. package/dist/core/web/scrapers/rubygems.d.ts.map +1 -0
  919. package/dist/core/web/scrapers/rubygems.js +23 -0
  920. package/dist/core/web/scrapers/rubygems.js.map +1 -0
  921. package/dist/core/web/scrapers/stackoverflow.d.ts +3 -0
  922. package/dist/core/web/scrapers/stackoverflow.d.ts.map +1 -0
  923. package/dist/core/web/scrapers/stackoverflow.js +46 -0
  924. package/dist/core/web/scrapers/stackoverflow.js.map +1 -0
  925. package/dist/core/web/scrapers/tipos.d.ts +42 -0
  926. package/dist/core/web/scrapers/tipos.d.ts.map +1 -0
  927. package/dist/core/web/scrapers/tipos.js +23 -0
  928. package/dist/core/web/scrapers/tipos.js.map +1 -0
  929. package/dist/core/web/texto.d.ts +10 -0
  930. package/dist/core/web/texto.d.ts.map +1 -0
  931. package/dist/core/web/texto.js +26 -0
  932. package/dist/core/web/texto.js.map +1 -0
  933. package/dist/core/web/tools.d.ts +91 -0
  934. package/dist/core/web/tools.d.ts.map +1 -0
  935. package/dist/core/web/tools.js +304 -0
  936. package/dist/core/web/tools.js.map +1 -0
  937. package/dist/core/wiki/almacen.d.ts +33 -0
  938. package/dist/core/wiki/almacen.d.ts.map +1 -0
  939. package/dist/core/wiki/almacen.js +93 -0
  940. package/dist/core/wiki/almacen.js.map +1 -0
  941. package/dist/core/wiki/citas.d.ts +111 -0
  942. package/dist/core/wiki/citas.d.ts.map +1 -0
  943. package/dist/core/wiki/citas.js +146 -0
  944. package/dist/core/wiki/citas.js.map +1 -0
  945. package/dist/core/workflows/agent-runner.d.ts +2 -0
  946. package/dist/core/workflows/agent-runner.d.ts.map +1 -1
  947. package/dist/core/workflows/agent-runner.js +131 -44
  948. package/dist/core/workflows/agent-runner.js.map +1 -1
  949. package/dist/core/workflows/builtins.d.ts.map +1 -1
  950. package/dist/core/workflows/builtins.js +1 -0
  951. package/dist/core/workflows/builtins.js.map +1 -1
  952. package/dist/core/workflows/contrato/esquema.d.ts +32 -0
  953. package/dist/core/workflows/contrato/esquema.d.ts.map +1 -0
  954. package/dist/core/workflows/contrato/esquema.js +25 -0
  955. package/dist/core/workflows/contrato/esquema.js.map +1 -0
  956. package/dist/core/workflows/contrato/reparar-argumentos.d.ts +69 -0
  957. package/dist/core/workflows/contrato/reparar-argumentos.d.ts.map +1 -0
  958. package/dist/core/workflows/contrato/reparar-argumentos.js +117 -0
  959. package/dist/core/workflows/contrato/reparar-argumentos.js.map +1 -0
  960. package/dist/core/workflows/contrato/validador.d.ts +30 -0
  961. package/dist/core/workflows/contrato/validador.d.ts.map +1 -0
  962. package/dist/core/workflows/contrato/validador.js +56 -0
  963. package/dist/core/workflows/contrato/validador.js.map +1 -0
  964. package/dist/core/workflows/contrato/yield.d.ts +65 -0
  965. package/dist/core/workflows/contrato/yield.d.ts.map +1 -0
  966. package/dist/core/workflows/contrato/yield.js +80 -0
  967. package/dist/core/workflows/contrato/yield.js.map +1 -0
  968. package/dist/core/workflows/goal-engine/engine.d.ts +17 -0
  969. package/dist/core/workflows/goal-engine/engine.d.ts.map +1 -1
  970. package/dist/core/workflows/goal-engine/engine.js +41 -0
  971. package/dist/core/workflows/goal-engine/engine.js.map +1 -1
  972. package/dist/core/workflows/index.d.ts +8 -0
  973. package/dist/core/workflows/index.d.ts.map +1 -1
  974. package/dist/core/workflows/index.js +4 -0
  975. package/dist/core/workflows/index.js.map +1 -1
  976. package/dist/core/workflows/manager.d.ts +10 -0
  977. package/dist/core/workflows/manager.d.ts.map +1 -1
  978. package/dist/core/workflows/manager.js +35 -11
  979. package/dist/core/workflows/manager.js.map +1 -1
  980. package/dist/core/workflows/runtime.d.ts +13 -0
  981. package/dist/core/workflows/runtime.d.ts.map +1 -1
  982. package/dist/core/workflows/runtime.js +80 -1
  983. package/dist/core/workflows/runtime.js.map +1 -1
  984. package/dist/core/workflows/save-as-skill.d.ts +27 -0
  985. package/dist/core/workflows/save-as-skill.d.ts.map +1 -0
  986. package/dist/core/workflows/save-as-skill.js +54 -0
  987. package/dist/core/workflows/save-as-skill.js.map +1 -0
  988. package/dist/core/workflows/types.d.ts +49 -0
  989. package/dist/core/workflows/types.d.ts.map +1 -1
  990. package/dist/core/workflows/types.js.map +1 -1
  991. package/dist/extensions/remoto/index.d.ts +22 -0
  992. package/dist/extensions/remoto/index.d.ts.map +1 -0
  993. package/dist/extensions/remoto/index.js +54 -0
  994. package/dist/extensions/remoto/index.js.map +1 -0
  995. package/dist/index.d.ts +1 -17
  996. package/dist/index.d.ts.map +1 -1
  997. package/dist/index.js +7 -16
  998. package/dist/index.js.map +1 -1
  999. package/dist/main.d.ts.map +1 -1
  1000. package/dist/main.js +6 -0
  1001. package/dist/main.js.map +1 -1
  1002. package/dist/modes/acp/acp-mode.d.ts +42 -0
  1003. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  1004. package/dist/modes/acp/acp-mode.js +190 -0
  1005. package/dist/modes/acp/acp-mode.js.map +1 -0
  1006. package/dist/modes/acp/events.d.ts +74 -0
  1007. package/dist/modes/acp/events.d.ts.map +1 -0
  1008. package/dist/modes/acp/events.js +116 -0
  1009. package/dist/modes/acp/events.js.map +1 -0
  1010. package/dist/modes/interactive/components/model-selector.d.ts +8 -1
  1011. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  1012. package/dist/modes/interactive/components/model-selector.js +41 -7
  1013. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  1014. package/dist/modes/interactive/components/settings-selector.d.ts +1 -2
  1015. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  1016. package/dist/modes/interactive/components/settings-selector.js +9 -86
  1017. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  1018. package/dist/modes/interactive/components/thinking-selector.d.ts +8 -1
  1019. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  1020. package/dist/modes/interactive/components/thinking-selector.js +48 -17
  1021. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  1022. package/dist/modes/interactive/interactive-mode.d.ts +3 -7
  1023. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  1024. package/dist/modes/interactive/interactive-mode.js +137 -146
  1025. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  1026. package/dist/modes/print-mode.d.ts.map +1 -1
  1027. package/dist/modes/print-mode.js +11 -0
  1028. package/dist/modes/print-mode.js.map +1 -1
  1029. package/dist/package-manager-cli.d.ts.map +1 -1
  1030. package/dist/package-manager-cli.js +27 -5
  1031. package/dist/package-manager-cli.js.map +1 -1
  1032. package/dist/server/attach.d.ts +56 -0
  1033. package/dist/server/attach.d.ts.map +1 -0
  1034. package/dist/server/attach.js +130 -0
  1035. package/dist/server/attach.js.map +1 -0
  1036. package/dist/server/extension-ops.d.ts +40 -0
  1037. package/dist/server/extension-ops.d.ts.map +1 -0
  1038. package/dist/server/extension-ops.js +146 -0
  1039. package/dist/server/extension-ops.js.map +1 -0
  1040. package/dist/server/pi-server-service.d.ts +85 -0
  1041. package/dist/server/pi-server-service.d.ts.map +1 -0
  1042. package/dist/server/pi-server-service.js +213 -0
  1043. package/dist/server/pi-server-service.js.map +1 -0
  1044. package/dist/server/scheduler-tick.d.ts +32 -0
  1045. package/dist/server/scheduler-tick.d.ts.map +1 -0
  1046. package/dist/server/scheduler-tick.js +47 -0
  1047. package/dist/server/scheduler-tick.js.map +1 -0
  1048. package/dist/server/serve.d.ts +25 -0
  1049. package/dist/server/serve.d.ts.map +1 -0
  1050. package/dist/server/serve.js +83 -0
  1051. package/dist/server/serve.js.map +1 -0
  1052. package/dist/server/transcript.d.ts +38 -0
  1053. package/dist/server/transcript.d.ts.map +1 -0
  1054. package/dist/server/transcript.js +176 -0
  1055. package/dist/server/transcript.js.map +1 -0
  1056. package/dist/skills/skill-creator/SKILL.md +90 -0
  1057. package/dist/telegram/api.d.ts +75 -0
  1058. package/dist/telegram/api.d.ts.map +1 -0
  1059. package/dist/telegram/api.js +104 -0
  1060. package/dist/telegram/api.js.map +1 -0
  1061. package/dist/telegram/bridge.d.ts +85 -0
  1062. package/dist/telegram/bridge.d.ts.map +1 -0
  1063. package/dist/telegram/bridge.js +182 -0
  1064. package/dist/telegram/bridge.js.map +1 -0
  1065. package/dist/telegram/journal.d.ts +67 -0
  1066. package/dist/telegram/journal.d.ts.map +1 -0
  1067. package/dist/telegram/journal.js +119 -0
  1068. package/dist/telegram/journal.js.map +1 -0
  1069. package/dist/telegram/servidor.d.ts +15 -0
  1070. package/dist/telegram/servidor.d.ts.map +1 -0
  1071. package/dist/telegram/servidor.js +74 -0
  1072. package/dist/telegram/servidor.js.map +1 -0
  1073. package/dist/utils/shell.d.ts +11 -0
  1074. package/dist/utils/shell.d.ts.map +1 -1
  1075. package/dist/utils/shell.js +11 -0
  1076. package/dist/utils/shell.js.map +1 -1
  1077. package/dist/utils/version-check.d.ts.map +1 -1
  1078. package/dist/utils/version-check.js +29 -1
  1079. package/dist/utils/version-check.js.map +1 -1
  1080. package/docs/compaction.md +10 -10
  1081. package/docs/compliance.md +241 -0
  1082. package/docs/containerization.md +24 -24
  1083. package/docs/custom-provider.md +10 -10
  1084. package/docs/development.md +47 -10
  1085. package/docs/docs.json +14 -2
  1086. package/docs/environment-variables.md +20 -17
  1087. package/docs/extensions.md +51 -49
  1088. package/docs/index.md +15 -21
  1089. package/docs/json.md +3 -3
  1090. package/docs/keybindings.md +5 -5
  1091. package/docs/llama-cpp.md +7 -7
  1092. package/docs/models.md +12 -12
  1093. package/docs/packages.md +45 -45
  1094. package/docs/prompt-templates.md +5 -5
  1095. package/docs/providers.md +12 -12
  1096. package/docs/quickstart.md +39 -39
  1097. package/docs/rpc.md +12 -12
  1098. package/docs/sdk.md +38 -38
  1099. package/docs/security.md +26 -21
  1100. package/docs/session-format.md +7 -7
  1101. package/docs/sessions.md +14 -14
  1102. package/docs/settings.md +45 -20
  1103. package/docs/shell-aliases.md +2 -2
  1104. package/docs/skills.md +11 -11
  1105. package/docs/terminal-setup.md +14 -14
  1106. package/docs/termux.md +7 -7
  1107. package/docs/themes.md +11 -11
  1108. package/docs/tmux.md +4 -4
  1109. package/docs/tui.md +3 -3
  1110. package/docs/usage.md +54 -54
  1111. package/docs/windows.md +2 -2
  1112. package/examples/sdk/12-full-control.ts +9 -0
  1113. package/npm-shrinkwrap.json +35 -251
  1114. package/package.json +10 -4
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AgentSession - Core abstraction for agent lifecycle and session management.\n *\n * This class is shared between all run modes (interactive, print, rpc).\n * It encapsulates:\n * - Agent state access\n * - Event subscription with automatic session persistence\n * - Model and thinking level management\n * - Compaction (manual and auto)\n * - Bash execution\n * - Session switching and branching\n *\n * Modes use this class and add their own I/O layer on top.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname } from \"node:path\";\nimport type {\n\tAgent,\n\tAgentEvent,\n\tAgentMessage,\n\tAgentState,\n\tAgentTool,\n\tPrepareNextTurnContext,\n\tThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport { contentText } from \"@earendil-works/pi-ai\";\nimport type {\n\tAssistantMessage,\n\tAuthResult,\n\tImageContent,\n\tModel,\n\tProviderHeaders,\n\tTextContent,\n\tUsage,\n} from \"@earendil-works/pi-ai/compat\";\nimport {\n\tclampThinkingLevel,\n\tcleanupSessionResources,\n\tgetSupportedThinkingLevels,\n\tisContextOverflow,\n\tisRecoverableLength,\n\tisRetryableAssistantError,\n\tmodelsAreEqual,\n\ttype RetryCallbacks,\n\tresetApiProviders,\n\tstreamSimple,\n} from \"@earendil-works/pi-ai/compat\";\nimport { getThemeByName, theme } from \"../modes/interactive/theme/theme.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { normalizeToolResultImages } from \"../utils/tool-result-images.ts\";\nimport { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport {\n\ttype CompactionPreparation,\n\ttype CompactionResult,\n\tcalculateContextTokens,\n\tcollectEntriesForBranchSummary,\n\tcompact,\n\testimateContextTokens,\n\testimateTokens,\n\tgenerateBranchSummary,\n\tprepareCompaction,\n\tshouldCompact,\n} from \"./compaction/index.ts\";\nimport { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from \"./defaults.ts\";\nimport { buildEnginePersonalityBlock } from \"./engine-personality.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport {\n\ttype ContextUsage,\n\ttype ExtensionCommandContextActions,\n\ttype ExtensionErrorListener,\n\ttype ExtensionMode,\n\tExtensionRunner,\n\ttype ExtensionUIContext,\n\ttype InputSource,\n\ttype MessageEndEvent,\n\ttype MessageStartEvent,\n\ttype MessageUpdateEvent,\n\ttype ReplacedSessionContext,\n\ttype SessionBeforeCompactResult,\n\ttype SessionBeforeTreeResult,\n\ttype SessionCompactFailedEvent,\n\ttype SessionStartEvent,\n\ttype ShutdownHandler,\n\ttype ToolDefinition,\n\ttype ToolExecutionEndEvent,\n\ttype ToolExecutionStartEvent,\n\ttype ToolExecutionUpdateEvent,\n\ttype ToolInfo,\n\ttype TreePreparation,\n\ttype TurnEndEvent,\n\ttype TurnStartEvent,\n\twrapRegisteredTools,\n} from \"./extensions/index.ts\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.ts\";\nimport { redactSensitive } from \"./memory-engine/security.ts\";\nimport { createMemoryEngineTools } from \"./memory-engine/tools.ts\";\nimport { expandVaultRefs } from \"./memory-engine/vault.ts\";\nimport type { BashExecutionMessage, CustomMessage } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport type { ModelRuntime } from \"./model-runtime.ts\";\nimport { loadProjectMemorySync, saveProjectMemory } from \"./project-memory.ts\";\nimport { optimizeUserPrompt } from \"./prompt-optimizer.ts\";\nimport { expandPromptTemplate, type PromptTemplate } from \"./prompt-templates.ts\";\nimport type { ResourceExtensionPaths, ResourceLoader } from \"./resource-loader.ts\";\nimport type { BranchSummaryEntry, CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\nimport { CURRENT_SESSION_VERSION, getLatestCompactionEntry, type SessionHeader } from \"./session-manager.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { SlashCommandInfo } from \"./slash-commands.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport { type BuildSystemPromptOptions, buildSystemPrompt } from \"./system-prompt.ts\";\nimport { type BashOperations, createLocalBashOperations } from \"./tools/bash.ts\";\nimport { createAllToolDefinitions } from \"./tools/index.ts\";\nimport { createToolDefinitionFromAgentTool } from \"./tools/tool-definition-wrapper.ts\";\nimport { addUsageToTotals, createUsageTotals } from \"./usage-totals.ts\";\nimport { goalSessionTools } from \"./workflows/goal-engine/tools.ts\";\nimport { createGraphTool, GraphSync } from \"./workflows/graph-sync.ts\";\nimport { createWorkflowTool } from \"./workflows/tool-workflow.ts\";\n\n// ============================================================================\n// Skill Block Parsing\n// ============================================================================\n\n/** Parsed skill block from a user message */\nexport interface ParsedSkillBlock {\n\tname: string;\n\tlocation: string;\n\tcontent: string;\n\tuserMessage: string | undefined;\n}\n\n/**\n * Parse a skill block from message text.\n * Returns null if the text doesn't contain a skill block.\n */\nexport function parseSkillBlock(text: string): ParsedSkillBlock | null {\n\tconst match = text.match(/^<skill name=\"([^\"]+)\" location=\"([^\"]+)\">\\n([\\s\\S]*?)\\n<\\/skill>(?:\\n\\n([\\s\\S]+))?$/);\n\tif (!match) return null;\n\treturn {\n\t\tname: match[1],\n\t\tlocation: match[2],\n\t\tcontent: match[3],\n\t\tuserMessage: match[4]?.trim() || undefined,\n\t};\n}\n\n/** Session-specific events that extend the core AgentEvent */\nexport type AgentSessionEvent =\n\t| Exclude<AgentEvent, { type: \"agent_end\" }>\n\t| {\n\t\t\ttype: \"agent_end\";\n\t\t\tmessages: AgentMessage[];\n\t\t\twillRetry: boolean;\n\t }\n\t| { type: \"agent_settled\" }\n\t| {\n\t\t\ttype: \"notification\";\n\t\t\tmessage: string;\n\t\t\tlevel?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"entry_appended\"; entry: SessionEntry }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: CompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| {\n\t\t\ttype: \"summarization_retry_scheduled\";\n\t\t\tattempt: number;\n\t\t\tmaxAttempts: number;\n\t\t\tdelayMs: number;\n\t\t\terrorMessage: string;\n\t }\n\t| { type: \"summarization_retry_attempt_start\"; source: \"branchSummary\" }\n\t| {\n\t\t\ttype: \"summarization_retry_attempt_start\";\n\t\t\tsource: \"compaction\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t }\n\t| { type: \"summarization_retry_finished\" }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| { type: \"bash_execution_update\"; id?: string; delta: string }\n\t| {\n\t\t\ttype: \"prompt_optimization\";\n\t\t\tdecision: \"clarify\" | \"enhance\";\n\t\t\toriginal: string;\n\t\t\tenhanced?: string;\n\t\t\tquestions?: string[];\n\t\t\tsummary?: string;\n\t };\n\n/** Listener function for agent session events */\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nfunction withoutDeletedHeaders(headers: ProviderHeaders | undefined): Record<string, string> | undefined {\n\treturn headers\n\t\t? Object.fromEntries(Object.entries(headers).filter((entry): entry is [string, string] => entry[1] !== null))\n\t\t: undefined;\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\t/** Models to cycle through with Ctrl+P (from --models flag) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\t/** Resource loader for extensions, skills, prompts, themes, context files, and system prompt */\n\tresourceLoader: ResourceLoader;\n\t/** SDK custom tools registered outside extensions */\n\tcustomTools?: ToolDefinition[];\n\t/** Subagentes: desactiva la continuación automática del GoalEngine (anti-recursión). */\n\tisSubagent?: boolean;\n\t/** Canonical model/auth runtime used by coding-agent internals. */\n\tmodelRuntime: ModelRuntime;\n\t/** Initial active built-in tool names. Default: [read, bash, edit, write] */\n\tinitialActiveToolNames?: string[];\n\t/** Optional allowlist of tool names. When provided, only these tool names are exposed. */\n\tallowedToolNames?: string[];\n\t/** Optional denylist of tool names. When provided, these tool names are not exposed. */\n\texcludedToolNames?: string[];\n\t/**\n\t * Override base tools (useful for custom runtimes).\n\t *\n\t * These are synthesized into minimal ToolDefinitions internally so AgentSession can keep\n\t * a definition-first registry even when callers provide plain AgentTool instances.\n\t */\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\t/** Mutable ref used by Agent to access the current ExtensionRunner */\n\textensionRunnerRef?: { current?: ExtensionRunner };\n\t/** Session start event metadata emitted when extensions bind to this runtime. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tmode?: ExtensionMode;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tabortHandler?: () => void;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\n/** Options for AgentSession.prompt() */\nexport interface PromptOptions {\n\t/** Whether to dispatch extension commands and expand skill commands and prompt templates (default: true) */\n\texpandPromptTemplates?: boolean;\n\t/** Image attachments */\n\timages?: ImageContent[];\n\t/** When streaming, how to queue the message: \"steer\" (interrupt) or \"followUp\" (wait). Required if streaming. */\n\tstreamingBehavior?: \"steer\" | \"followUp\";\n\t/** Source of input for extension input event handlers. Defaults to \"interactive\". */\n\tsource?: InputSource;\n\t/** Internal hook used by RPC mode to observe prompt preflight acceptance or rejection. */\n\tpreflightResult?: (success: boolean) => void;\n}\n\n/** Options for model/thinking mutations. */\nexport interface ModelMutationOptions {\n\t/** Persist the new value to global defaults. Defaults to session-only. */\n\tpersist?: boolean;\n}\n\n/** Result from cycleModel() */\nexport interface ModelCycleResult {\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\t/** Whether cycling through scoped models (--models flag) or all available */\n\tisScoped: boolean;\n}\n\n/** Session statistics for /session command */\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\ninterface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\nfunction estimateMessagesTokens(messages: AgentMessage[]): number {\n\tlet tokens = 0;\n\tfor (const message of messages) {\n\t\ttokens += estimateTokens(message);\n\t}\n\treturn tokens;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n// ============================================================================\n// AgentSession Class\n// ============================================================================\n\nexport class AgentSession {\n\treadonly agent: Agent;\n\treadonly sessionManager: SessionManager;\n\treadonly settingsManager: SettingsManager;\n\n\tprivate _scopedModels: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t// Event subscription state\n\tprivate _unsubscribeAgent?: () => void;\n\tprivate _eventListeners: AgentSessionEventListener[] = [];\n\tprivate _isAgentRunActive = false;\n\tprivate _idleWaitPromise: Promise<void> | undefined;\n\tprivate _resolveIdleWait: (() => void) | undefined;\n\n\t/** Tracks pending steering messages for UI display. Removed when delivered. */\n\tprivate _steeringMessages: string[] = [];\n\t/** Tracks pending follow-up messages for UI display. Removed when delivered. */\n\tprivate _followUpMessages: string[] = [];\n\t/** Messages queued to be included with the next user prompt as context (\"asides\"). */\n\tprivate _pendingNextTurnMessages: CustomMessage[] = [];\n\n\t// Compaction state\n\tprivate _compactionAbortController: AbortController | undefined = undefined;\n\tprivate _autoCompactionAbortController: AbortController | undefined = undefined;\n\tprivate _overflowRecoveryAttempted = false;\n\n\t// Branch summarization state\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\t// Retry state\n\tprivate _retryAbortController: AbortController | undefined = undefined;\n\tprivate _retryAttempt = 0;\n\n\t// Bash execution state\n\tprivate readonly _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\t// Extension system\n\tprivate _extensionRunner!: ExtensionRunner;\n\tprivate _turnIndex = 0;\n\n\tprivate _resourceLoader: ResourceLoader;\n\tprivate _customTools: ToolDefinition[];\n\tprivate _isSubagentSession = false;\n\tprivate _graphSync?: GraphSync;\n\tprivate _baseToolDefinitions: Map<string, ToolDefinition> = new Map();\n\tprivate _cwd: string;\n\tprivate _extensionRunnerRef?: { current?: ExtensionRunner };\n\tprivate _initialActiveToolNames?: string[];\n\tprivate _allowedToolNames?: Set<string>;\n\tprivate _excludedToolNames?: Set<string>;\n\tprivate _baseToolsOverride?: Record<string, AgentTool>;\n\tprivate _sessionStartEvent: SessionStartEvent;\n\tprivate _extensionUIContext?: ExtensionUIContext;\n\tprivate _extensionMode: ExtensionMode = \"print\";\n\tprivate _extensionCommandContextActions?: ExtensionCommandContextActions;\n\tprivate _extensionAbortHandler?: () => void;\n\tprivate _extensionShutdownHandler?: ShutdownHandler;\n\tprivate _extensionErrorListener?: ExtensionErrorListener;\n\tprivate _extensionErrorUnsubscriber?: () => void;\n\n\tprivate _modelRuntime: ModelRuntime;\n\n\t/** Optimizador autónomo de prompts: \"off\" | \"auto\" | \"clarify\". */\n\tprivate _promptOptimizerMode: \"off\" | \"auto\" | \"clarify\" = \"off\";\n\n\t/** Clarificación pendiente (modo \"clarify\"): original + preguntas ya emitidas. */\n\tprivate _pendingClarification: { original: string; questions: string[]; answers?: string[] } | undefined;\n\n\t// Tool registry for extension getTools/setTools\n\tprivate _toolRegistry: Map<string, AgentTool> = new Map();\n\tprivate _toolDefinitions: Map<string, ToolDefinitionEntry> = new Map();\n\tprivate _toolPromptSnippets: Map<string, string> = new Map();\n\tprivate _toolPromptGuidelines: Map<string, string[]> = new Map();\n\n\t// Base system prompt (without extension appends) - used to apply fresh appends each turn\n\tprivate _baseSystemPrompt = \"\";\n\tprivate _baseSystemPromptOptions!: BuildSystemPromptOptions;\n\tprivate _systemPromptOverride?: string;\n\n\tconstructor(config: AgentSessionConfig) {\n\t\tthis.agent = config.agent;\n\t\tthis.sessionManager = config.sessionManager;\n\t\tthis.settingsManager = config.settingsManager;\n\t\tthis._scopedModels = config.scopedModels ?? [];\n\t\tthis._resourceLoader = config.resourceLoader;\n\t\tthis._customTools = config.customTools ?? [];\n\t\tthis._isSubagentSession = config.isSubagent === true;\n\t\tthis._cwd = config.cwd;\n\t\tthis._modelRuntime = config.modelRuntime;\n\t\tthis._extensionRunnerRef = config.extensionRunnerRef;\n\t\tthis._initialActiveToolNames = config.initialActiveToolNames;\n\t\tthis._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;\n\t\tthis._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;\n\t\tthis._baseToolsOverride = config.baseToolsOverride;\n\t\tthis._sessionStartEvent = config.sessionStartEvent ?? { type: \"session_start\", reason: \"startup\" };\n\n\t\t// Always subscribe to agent events for internal handling\n\t\t// (session persistence, extensions, auto-compaction, retry logic)\n\t\tthis._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);\n\t\tthis._installAgentToolHooks();\n\t\tthis._installAgentNextTurnRefresh();\n\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this._initialActiveToolNames,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\n\t\t// Los subagentes (goals/workflows) HEREDAN el modelo de la sesión principal:\n\t\t// misma provider/modelId → misma auth → misma API key del chat.\n\t\tthis._resourceLoader.setSessionModelGetter?.(() => {\n\t\t\tconst m = this.model;\n\t\t\treturn m\n\t\t\t\t? {\n\t\t\t\t\t\tprovider: m.provider,\n\t\t\t\t\t\tmodelId: (m as { modelId?: string; id?: string }).modelId ?? (m as { id?: string }).id ?? \"\",\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\t});\n\n\t\t// Cuando un run de workflow arranca (tool del agente o sub-tarea del\n\t\t// goal), la sesión emite workflow_started — el desktop lo usa para\n\t\t// abrir el panel Engine automáticamente.\n\t\tthis._resourceLoader.setOnWorkflowRunStarted?.((runId, name) => {\n\t\t\tthis._emit({ type: \"workflow_started\", runId, name } as never);\n\t\t});\n\n\t\t// Ada Memory Engine: lazy-initialize (stores, migration, session index)\n\t\t// and wire the LLM callback used by review/correction/flush. Best-effort\n\t\t// and never blocks startup.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine) {\n\t\t\tmemoryEngine.setSessionId(this.sessionManager.getSessionId());\n\t\t\tmemoryEngine.setComplete(async (systemPrompt: string, userContent: string) => {\n\t\t\t\tconst model = this.model;\n\t\t\t\tif (!model) throw new Error(\"No model selected\");\n\t\t\t\tconst result = await this._modelRuntime.completeSimple(model, {\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t\tmessages: [{ role: \"user\", content: userContent }],\n\t\t\t\t} as never);\n\t\t\t\treturn contentText(result.content, \"\").trim();\n\t\t\t});\n\t\t\tmemoryEngine.setNotify((message, level) => {\n\t\t\t\tthis._emit({ type: \"notification\", message, level });\n\t\t\t});\n\t\t\tmemoryEngine.setSensitiveDetected((reason) => {\n\t\t\t\t// Persistent, highlighted message inside the conversation (CLI +\n\t\t\t\t// desktop) — never a transient banner.\n\t\t\t\tvoid this.sendCustomMessage({\n\t\t\t\t\tcustomType: \"sensitive-notice\",\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Has introducido un dato sensible que no se almacenará en memoria. ${reason}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\tdetails: { engine: \"ada-memory\", kind: \"sensitive\" },\n\t\t\t\t});\n\t\t\t});\n\t\t\tvoid memoryEngine.initialize().catch(() => {\n\t\t\t\t// Memory must never break the session.\n\t\t\t});\n\t\t}\n\t}\n\n\tget modelRuntime(): ModelRuntime {\n\t\treturn this._modelRuntime;\n\t}\n\n\tprivate async _getRequiredRequestAuth(model: Model<any>): Promise<{\n\t\tmodel: Model<any>;\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t\tenv?: Record<string, string>;\n\t}> {\n\t\tlet result: AuthResult | undefined;\n\t\ttry {\n\t\t\tresult = await this._modelRuntime.getAuth(model);\n\t\t} catch (error) {\n\t\t\tconst cause = error instanceof Error ? error.cause : undefined;\n\t\t\tif (cause instanceof Error && cause.message === \"authHeader requires a resolved API key\") {\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tif (result && (result.auth.apiKey || result.auth.headers)) {\n\t\t\tconst requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;\n\t\t\treturn {\n\t\t\t\tmodel: requestModel,\n\t\t\t\tapiKey: result.auth.apiKey,\n\t\t\t\theaders: withoutDeletedHeaders(result.auth.headers),\n\t\t\t\tenv: result.env,\n\t\t\t};\n\t\t}\n\n\t\tconst isOAuth = this._modelRuntime.isUsingOAuth(model.provider);\n\t\tif (isOAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _getSummarizationRequestAuth(model: Model<any>): Promise<{\n\t\tmodel: Model<any>;\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t\tenv?: Record<string, string>;\n\t}> {\n\t\tif (this.agent.streamFunction === streamSimple) {\n\t\t\treturn this._getRequiredRequestAuth(model);\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this._modelRuntime.getAuth(model);\n\t\t\tif (!result) return { model };\n\t\t\tconst requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;\n\t\t\treturn {\n\t\t\t\tmodel: requestModel,\n\t\t\t\tapiKey: result.auth.apiKey,\n\t\t\t\theaders: withoutDeletedHeaders(result.auth.headers),\n\t\t\t\tenv: result.env,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn { model };\n\t\t}\n\t}\n\n\t/**\n\t * Install tool hooks once on the Agent instance.\n\t *\n\t * The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the\n\t * new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt\n\t * registered tool execution to the extension context. Tool call and tool result interception now\n\t * happens here instead of in wrappers.\n\t */\n\tprivate _installAgentToolHooks(): void {\n\t\tthis.agent.beforeToolCall = async ({ toolCall, args }) => {\n\t\t\tconst runner = this._extensionRunner;\n\t\t\tif (!runner.hasHandlers(\"tool_call\")) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn await runner.emitToolCall({\n\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(err)}`);\n\t\t\t}\n\t\t};\n\n\t\tthis.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {\n\t\t\t// Auto-registro del grafo (best-effort, nunca bloquea el agente).\n\t\t\ttry {\n\t\t\t\tif (!this._graphSync) {\n\t\t\t\t\tthis._graphSync = new GraphSync({\n\t\t\t\t\t\tgraph: () => this._resourceLoader.getGraph?.(),\n\t\t\t\t\t\tcwd: this._cwd,\n\t\t\t\t\t\tactiveGoal: () => null, // resuelto en agent_settled vía syncGoal\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tvoid this._graphSync.onToolResult(toolCall.name, args as Record<string, unknown>, result, isError);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t\tconst runner = this._extensionRunner;\n\t\t\tconst hookResult = runner.hasHandlers(\"tool_result\")\n\t\t\t\t? await runner.emitToolResult({\n\t\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t\t\tcontent: result.content,\n\t\t\t\t\t\tdetails: result.details,\n\t\t\t\t\t\tisError,\n\t\t\t\t\t\tusage: result.usage,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\n\t\t\tconst content = hookResult?.content ?? result.content ?? [];\n\t\t\t// Runs after the extension hook so images injected or replaced by extensions are normalized too.\n\t\t\tconst normalizedContent = await normalizeToolResultImages(content, {\n\t\t\t\tautoResizeImages: this.settingsManager.getImageAutoResize(),\n\t\t\t});\n\n\t\t\tif (!hookResult && normalizedContent === content) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: normalizedContent,\n\t\t\t\tdetails: hookResult?.details,\n\t\t\t\tisError: hookResult?.isError ?? isError,\n\t\t\t\tusage: hookResult?.usage,\n\t\t\t};\n\t\t};\n\t}\n\n\tprivate _installAgentNextTurnRefresh(): void {\n\t\tconst previousPrepareNextTurnWithContext =\n\t\t\tthis.agent.prepareNextTurnWithContext ??\n\t\t\t(this.agent.prepareNextTurn\n\t\t\t\t? async (_turn: PrepareNextTurnContext, signal?: AbortSignal) => await this.agent.prepareNextTurn?.(signal)\n\t\t\t\t: undefined);\n\t\tthis.agent.prepareNextTurnWithContext = async (turn, signal) => {\n\t\t\tconst previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);\n\t\t\tconst previousContext = previousSnapshot?.context ?? turn.context;\n\n\t\t\treturn {\n\t\t\t\t...previousSnapshot,\n\t\t\t\tcontext: {\n\t\t\t\t\t...previousContext,\n\t\t\t\t\tsystemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,\n\t\t\t\t\ttools: this.agent.state.tools.slice(),\n\t\t\t\t},\n\t\t\t\tmodel: this.agent.state.model,\n\t\t\t\tthinkingLevel: this.agent.state.thinkingLevel,\n\t\t\t};\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Event Subscription\n\t// =========================================================================\n\n\t/** Emit an event to all listeners */\n\tprivate _emit(event: AgentSessionEvent): void {\n\t\tfor (const l of this._eventListeners) {\n\t\t\tl(event);\n\t\t}\n\t}\n\n\tprivate _emitQueueUpdate(): void {\n\t\tthis._emit({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteering: [...this._steeringMessages],\n\t\t\tfollowUp: [...this._followUpMessages],\n\t\t});\n\t}\n\n\tprivate async _emitSessionCompactFailed(event: Omit<SessionCompactFailedEvent, \"type\">): Promise<void> {\n\t\tif (this._extensionRunner.hasHandlers(\"session_compact_failed\")) {\n\t\t\tawait this._extensionRunner.emit({ type: \"session_compact_failed\", ...event });\n\t\t}\n\t}\n\n\tprivate _getIdleWaitPromise(): Promise<void> {\n\t\tif (!this._idleWaitPromise) {\n\t\t\tthis._idleWaitPromise = new Promise((resolve) => {\n\t\t\t\tthis._resolveIdleWait = resolve;\n\t\t\t});\n\t\t}\n\t\treturn this._idleWaitPromise;\n\t}\n\n\tprivate _resolveIdleWaitIfIdle(): void {\n\t\tif (this._isAgentRunActive || !this._resolveIdleWait) {\n\t\t\treturn;\n\t\t}\n\t\tconst resolve = this._resolveIdleWait;\n\t\tthis._idleWaitPromise = undefined;\n\t\tthis._resolveIdleWait = undefined;\n\t\tresolve();\n\t}\n\n\tprivate async _emitAgentSettled(): Promise<void> {\n\t\tthis._isAgentRunActive = false;\n\t\ttry {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_settled\" });\n\t\t\tthis._emit({ type: \"agent_settled\" });\n\t\t\t// Ada Intelligence Engine: el GoalEngine es el ÚNICO dueño de la\n\t\t\t// continuación automática del goal (pi-goal). No compite con retry/\n\t\t\t// compaction/steer. Fire-and-forget; nunca bloquea ni rompe el chat.\n\t\t\ttry {\n\t\t\t\tconst goalEngine = this._isSubagentSession ? null : this._resourceLoader.getGoalEngine?.();\n\t\t\t\tif (goalEngine) {\n\t\t\t\t\tvoid this._maybeContinueGoal(goalEngine);\n\t\t\t\t\t// Spine del grafo: sync automático del goal activo.\n\t\t\t\t\tif (this._graphSync) {\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\tconst goals = await goalEngine.list().catch(() => []);\n\t\t\t\t\t\t\tconst active = goals.find((g) => g.status === \"active\");\n\t\t\t\t\t\t\tif (active) {\n\t\t\t\t\t\t\t\tconst full = await goalEngine.get(active.id).catch(() => null);\n\t\t\t\t\t\t\t\tif (full) {\n\t\t\t\t\t\t\t\t\tawait this._graphSync?.syncGoal({\n\t\t\t\t\t\t\t\t\t\tid: full.id,\n\t\t\t\t\t\t\t\t\t\tgoal: full.goal,\n\t\t\t\t\t\t\t\t\t\tphase: full.phase,\n\t\t\t\t\t\t\t\t\t\ttasks: full.tasks ?? [],\n\t\t\t\t\t\t\t\t\t\tdoneCriteria: full.doneCriteria ?? [],\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._resolveIdleWaitIfIdle();\n\t\t}\n\t}\n\n\t/**\n\t * Confirmación fail-closed del lanzamiento de workflows: emite un evento y\n\t * espera respuesta humana hasta 15s; sin respuesta → NO lanzar.\n\t */\n\tprivate _confirmWorkflowLaunch(prompt: string): Promise<boolean> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis._confirmResolvers.delete(prompt);\n\t\t\t\tresolve(false);\n\t\t\t}, 15_000);\n\t\t\tthis._confirmResolvers.set(prompt, (ok: boolean) => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._confirmResolvers.delete(prompt);\n\t\t\t\tresolve(ok);\n\t\t\t});\n\t\t\tthis._emit({ type: \"notification\", message: `[workflow] ${prompt}`, level: \"warning\" });\n\t\t});\n\t}\n\n\t/** Respuesta a una confirmación pendiente (la UI la llama). */\n\tresolveWorkflowConfirmation(prompt: string, ok: boolean): boolean {\n\t\tconst resolver = this._confirmResolvers.get(prompt);\n\t\tif (!resolver) return false;\n\t\tresolver(ok);\n\t\treturn true;\n\t}\n\n\t/** Si hay un goal activo aprobado, avanza un turno del bucle. */\n\tprivate async _maybeContinueGoal(goalEngine: import(\"./workflows/goal-engine/engine.ts\").GoalEngine): Promise<void> {\n\t\tconst goals = await goalEngine.list().catch(() => []);\n\t\tconst active = goals.find((g) => g.status === \"active\");\n\t\tif (!active) return;\n\t\t// Freno por defecto del engine: si está desactivado no continúa.\n\t\tif (!this.settingsManager.getGoalEngineEnabled()) return;\n\t\t// markers anti-duplicados: solo avanzar si no hay un turno ya en curso.\n\t\tif (this._isAgentRunActive) return;\n\t\tthis._isAgentRunActive = true;\n\t\ttry {\n\t\t\tawait goalEngine.tick(active.id).catch(() => {});\n\t\t} finally {\n\t\t\tthis._isAgentRunActive = false;\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Automatic project memory (claude-mem style)\n\t// =========================================================================\n\n\t/**\n\t * Replace vault-expanded secrets with the redacted form for UI events and\n\t * persistence (the LLM still receives the expanded values via agent state).\n\t */\n\tprivate _applyVaultRedaction(event: { message: AgentMessage }): void {\n\t\tif (event.message.role !== \"user\") return;\n\t\tconst id = (event.message as { id?: string }).id;\n\t\tif (!id) return;\n\t\tconst redactedText = this._vaultRedactions.get(id);\n\t\tif (redactedText === undefined) return;\n\t\tthis._vaultRedactions.delete(id);\n\t\tevent.message = {\n\t\t\t...event.message,\n\t\t\tcontent: [{ type: \"text\", text: redactedText }],\n\t\t} as AgentMessage;\n\t}\n\n\t/** Live-index a message for the Memory Engine session search (layer 3c). */\n\tprivate _indexMessageForMemory(message: {\n\t\tid?: string;\n\t\trole?: string;\n\t\tcontent?: unknown;\n\t\ttimestamp?: string | number;\n\t}): void {\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (!memoryEngine?.isInitialized) return;\n\t\ttry {\n\t\t\tmemoryEngine.indexSessionMessage({\n\t\t\t\tid: message.id,\n\t\t\t\trole: message.role,\n\t\t\t\tcontent: message.content,\n\t\t\t\ttimestamp: message.timestamp,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Best-effort indexing.\n\t\t}\n\t}\n\n\tprivate static readonly AUTO_MEMORIZE_MIN_INTERVAL_MS = 45_000;\n\n\tprivate static readonly AUTO_MEMORIZE_SYSTEM_PROMPT = `You maintain the shared project memory of a coding project.\nThe memory is loaded into the context of every conversation of this project, so it must stay concise and durable.\n\nTask: given the CURRENT MEMORY and the LATEST CONVERSATION TURN, produce the COMPLETE new memory content in Markdown.\n- Keep everything already in the current memory that is still true.\n- Add durable facts from the turn: architecture, conventions, decisions, commands, project layout, user preferences.\n- Ignore transient details (specific code snippets, error messages, formatting fixes, small refactors).\n- If nothing durable and new was said, reply with exactly: NO_CHANGE\n- Keep it under ~600 words. Use short bullet lists.\n- Reply with ONLY the memory content (or NO_CHANGE).`;\n\n\tprivate async _maybeAutoMemorize(event: Extract<AgentEvent, { type: \"agent_end\" }>): Promise<void> {\n\t\ttry {\n\t\t\tif (!this.settingsManager.getProjectMemoryEnabled()) return;\n\t\t\tconst now = Date.now();\n\t\t\tif (now - this._lastAutoMemorizeAt < AgentSession.AUTO_MEMORIZE_MIN_INTERVAL_MS) return;\n\t\t\tconst model = this.model;\n\t\t\tif (!model) return;\n\n\t\t\t// Only turns with a real user message followed by a successful assistant reply.\n\t\t\tconst messages = event.messages;\n\t\t\tconst hasUserMessage = messages.some((m) => m.role === \"user\" && contentText(m.content, \"\").trim().length > 0);\n\t\t\tconst lastAssistant = [...messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tif (!hasUserMessage || !lastAssistant) return;\n\t\t\tconst lastAssistantText = contentText(lastAssistant.content, \"\").trim();\n\t\t\tif (!lastAssistantText) return;\n\n\t\t\t// Build a compact transcript of the turn (user + assistant + tool summaries).\n\t\t\tconst transcriptLines: string[] = [];\n\t\t\tfor (const m of messages) {\n\t\t\t\tif (m.role === \"user\") {\n\t\t\t\t\ttranscriptLines.push(`USER: ${contentText(m.content, \"\").trim().slice(0, 2000)}`);\n\t\t\t\t} else if (m.role === \"assistant\") {\n\t\t\t\t\ttranscriptLines.push(`ASSISTANT: ${contentText(m.content, \"\").trim().slice(0, 2000)}`);\n\t\t\t\t} else if (m.role === \"toolResult\") {\n\t\t\t\t\ttranscriptLines.push(`(tool result)`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst transcript = transcriptLines.join(\"\\n\").slice(0, 12_000);\n\t\t\tif (transcript.trim().length === 0) return;\n\n\t\t\tconst agentDir = this._resourceLoader.getAgentDir();\n\t\t\tconst currentMemory = loadProjectMemorySync(this._cwd, agentDir);\n\n\t\t\tconst result = await this._modelRuntime.completeSimple(model, {\n\t\t\t\tsystemPrompt: AgentSession.AUTO_MEMORIZE_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: `CURRENT MEMORY:\\n${currentMemory?.content ?? \"(none)\"}\\n\\nLATEST CONVERSATION TURN:\\n${transcript}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t} as never);\n\t\t\tconst newContent = contentText(result.content, \"\").trim();\n\t\t\tif (!newContent || newContent === \"NO_CHANGE\") return;\n\t\t\tif (newContent === currentMemory?.content) return;\n\n\t\t\tthis._lastAutoMemorizeAt = now;\n\t\t\tawait saveProjectMemory(this._cwd, agentDir, newContent);\n\t\t} catch {\n\t\t\t// Memorization must never break the conversation flow.\n\t\t}\n\t}\n\n\t// Track last assistant message for auto-compaction check\n\tprivate _lastAssistantMessage: AssistantMessage | undefined = undefined;\n\tprivate _lastAutoMemorizeAt = 0;\n\tprivate _confirmResolvers = new Map<string, (ok: boolean) => void>();\n\t/** Vault expansion redactions by user-message id (frontend + persistence). */\n\tprivate _vaultRedactions = new Map<string, string>();\n\tprivate _userMessageSeq = 0;\n\t/** Pending expansion for the user message built right after (prompt flow). */\n\tprivate _pendingVaultExpansion: { id: string; redactedText: string } | null = null;\n\n\t/** Internal handler for agent events - shared by subscribe and reconnect */\n\tprivate _handleAgentEvent = async (event: AgentEvent): Promise<void> => {\n\t\t// When a user message starts, check if it's from either queue and remove it BEFORE emitting\n\t\t// This ensures the UI sees the updated queue state\n\t\tif (event.type === \"message_start\" && event.message.role === \"user\") {\n\t\t\tthis._overflowRecoveryAttempted = false;\n\t\t\tconst messageText = contentText(event.message.content, \"\");\n\t\t\tif (messageText) {\n\t\t\t\t// Check steering queue first\n\t\t\t\tconst steeringIndex = this._steeringMessages.indexOf(messageText);\n\t\t\t\tif (steeringIndex !== -1) {\n\t\t\t\t\tthis._steeringMessages.splice(steeringIndex, 1);\n\t\t\t\t\tthis._emitQueueUpdate();\n\t\t\t\t} else {\n\t\t\t\t\t// Check follow-up queue\n\t\t\t\t\tconst followUpIndex = this._followUpMessages.indexOf(messageText);\n\t\t\t\t\tif (followUpIndex !== -1) {\n\t\t\t\t\t\tthis._followUpMessages.splice(followUpIndex, 1);\n\t\t\t\t\t\tthis._emitQueueUpdate();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Emit to extensions first\n\t\tawait this._emitExtensionEvent(event);\n\n\t\t// Notify all listeners\n\t\tthis._emit(event.type === \"agent_end\" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);\n\n\t\t// Ada Memory Engine: track user turns / corrections, run the\n\t\t// background review loop, and live-index messages for session search.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine?.isInitialized) {\n\t\t\tif (event.type === \"message_end\") {\n\t\t\t\tmemoryEngine.onMessageEnd(event.message);\n\t\t\t\tthis._indexMessageForMemory(event.message);\n\t\t\t} else if (event.type === \"turn_end\") {\n\t\t\t\tvoid memoryEngine.onTurnEnd({ message: event.message });\n\t\t\t}\n\t\t}\n\n\t\t// Automatic project memory (claude-mem style): after each completed\n\t\t// agent turn with real user interaction, distill durable facts into\n\t\t// the shared project memory. Fire-and-forget; never blocks the turn.\n\t\t// Disabled when the Memory Engine is active (its review loop replaces it).\n\t\tif (event.type === \"agent_end\") {\n\t\t\tif (!memoryEngine?.isInitialized) {\n\t\t\t\tvoid this._maybeAutoMemorize(event);\n\t\t\t}\n\t\t}\n\n\t\t// Handle session persistence\n\t\tif (event.type === \"message_end\") {\n\t\t\t// Check if this is a custom message from extensions\n\t\t\tif (event.message.role === \"custom\") {\n\t\t\t\t// Persist as CustomMessageEntry\n\t\t\t\tthis.sessionManager.appendCustomMessageEntry(\n\t\t\t\t\tevent.message.customType,\n\t\t\t\t\tevent.message.content,\n\t\t\t\t\tevent.message.display,\n\t\t\t\t\tevent.message.details,\n\t\t\t\t);\n\t\t\t} else if (\n\t\t\t\tevent.message.role === \"user\" ||\n\t\t\t\tevent.message.role === \"assistant\" ||\n\t\t\t\tevent.message.role === \"toolResult\"\n\t\t\t) {\n\t\t\t\t// Regular LLM message - persist as SessionMessageEntry. Vault\n\t\t\t\t// expansions are already redacted by _applyVaultRedaction above.\n\t\t\t\tthis.sessionManager.appendMessage(event.message);\n\t\t\t}\n\t\t\t// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere\n\n\t\t\t// Track assistant message for auto-compaction (checked on agent_end)\n\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\tthis._lastAssistantMessage = event.message;\n\n\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason !== \"error\" && assistantMsg.stopReason !== \"length\") {\n\t\t\t\t\tthis._overflowRecoveryAttempted = false;\n\t\t\t\t}\n\n\t\t\t\t// Reset retry counter immediately on successful assistant response\n\t\t\t\t// This prevents accumulation across multiple LLM calls within a turn\n\t\t\t\tif (assistantMsg.stopReason !== \"error\" && this._retryAttempt > 0) {\n\t\t\t\t\tthis._emit({\n\t\t\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tattempt: this._retryAttempt,\n\t\t\t\t\t});\n\t\t\t\t\tthis._retryAttempt = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tprivate _willRetryAfterAgentEnd(event: Extract<AgentEvent, { type: \"agent_end\" }>): boolean {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled || this._retryAttempt >= settings.maxRetries) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let i = event.messages.length - 1; i >= 0; i--) {\n\t\t\tconst message = event.messages[i];\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\treturn this._isRetryableError(message as AssistantMessage);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Find the last assistant message in agent state (including aborted ones) */\n\tprivate _findLastAssistantMessage(): AssistantMessage | undefined {\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\t\tconst msg = messages[i];\n\t\t\tif (msg.role === \"assistant\") {\n\t\t\t\treturn msg as AssistantMessage;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {\n\t\t// Agent-core stores the finalized message object in its state before emitting message_end.\n\t\t// SessionManager persistence happens later in _handleAgentEvent() with event.message.\n\t\t// Mutating this object in place keeps agent state, later turn/agent events, listeners,\n\t\t// and the eventual SessionManager.appendMessage(event.message) persistence in sync.\n\t\tif (target === replacement) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetRecord = target as unknown as Record<string, unknown>;\n\t\tfor (const key of Object.keys(targetRecord)) {\n\t\t\tdelete targetRecord[key];\n\t\t}\n\t\tObject.assign(targetRecord, replacement);\n\t}\n\n\t/** Emit extension events based on agent events */\n\tprivate async _emitExtensionEvent(event: AgentEvent): Promise<void> {\n\t\tif (event.type === \"agent_start\") {\n\t\t\tthis._turnIndex = 0;\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_start\" });\n\t\t} else if (event.type === \"agent_end\") {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_end\", messages: event.messages });\n\t\t} else if (event.type === \"turn_start\") {\n\t\t\tconst extensionEvent: TurnStartEvent = {\n\t\t\t\ttype: \"turn_start\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst extensionEvent: TurnEndEvent = {\n\t\t\t\ttype: \"turn_end\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\tmessage: event.message,\n\t\t\t\ttoolResults: event.toolResults,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t\tthis._turnIndex++;\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tthis._applyVaultRedaction(event);\n\t\t\tconst extensionEvent: MessageStartEvent = {\n\t\t\t\ttype: \"message_start\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_update\") {\n\t\t\tconst extensionEvent: MessageUpdateEvent = {\n\t\t\t\ttype: \"message_update\",\n\t\t\t\tmessage: event.message,\n\t\t\t\tassistantMessageEvent: event.assistantMessageEvent,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tthis._applyVaultRedaction(event);\n\t\t\tconst extensionEvent: MessageEndEvent = {\n\t\t\t\ttype: \"message_end\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tconst replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);\n\t\t\tif (replacement) {\n\t\t\t\t// Untyped extension handlers can return messages with null/missing content;\n\t\t\t\t// normalize so it never enters agent state or session history.\n\t\t\t\tconst normalized =\n\t\t\t\t\t(replacement.role === \"user\" ||\n\t\t\t\t\t\treplacement.role === \"assistant\" ||\n\t\t\t\t\t\treplacement.role === \"toolResult\" ||\n\t\t\t\t\t\treplacement.role === \"custom\") &&\n\t\t\t\t\treplacement.content == null\n\t\t\t\t\t\t? ({ ...replacement, content: [] } as AgentMessage)\n\t\t\t\t\t\t: replacement;\n\t\t\t\tthis._replaceMessageInPlace(event.message, normalized);\n\t\t\t}\n\t\t} else if (event.type === \"tool_execution_start\") {\n\t\t\tconst extensionEvent: ToolExecutionStartEvent = {\n\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_update\") {\n\t\t\tconst extensionEvent: ToolExecutionUpdateEvent = {\n\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t\tpartialResult: event.partialResult,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst extensionEvent: ToolExecutionEndEvent = {\n\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\tresult: event.result,\n\t\t\t\tisError: event.isError,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t * Session persistence is handled internally (saves messages on message_end).\n\t * Multiple listeners can be added. Returns unsubscribe function for this listener.\n\t */\n\tsubscribe(listener: AgentSessionEventListener): () => void {\n\t\tthis._eventListeners.push(listener);\n\n\t\t// Return unsubscribe function for this specific listener\n\t\treturn () => {\n\t\t\tconst index = this._eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis._eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/** Disconnect from agent events during disposal. */\n\tprivate _disconnectFromAgent(): void {\n\t\tif (this._unsubscribeAgent) {\n\t\t\tthis._unsubscribeAgent();\n\t\t\tthis._unsubscribeAgent = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Remove all listeners and disconnect from agent.\n\t * Call this when completely done with the session.\n\t */\n\tdispose(): void {\n\t\ttry {\n\t\t\tthis.abortRetry();\n\t\t\tthis.abortCompaction();\n\t\t\tthis.abortBranchSummary();\n\t\t\tthis.abortBash();\n\t\t\tthis.agent.abort();\n\t\t} catch {\n\t\t\t// Dispose must succeed even if an abort hook throws.\n\t\t}\n\n\t\t// Ada Memory Engine: persist the session index (best-effort).\n\t\ttry {\n\t\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\t\tif (memoryEngine?.isInitialized) {\n\t\t\t\tvoid memoryEngine.onShutdown();\n\t\t\t}\n\t\t} catch {\n\t\t\t// Dispose must succeed even if memory persistence fails.\n\t\t}\n\n\t\tthis._extensionRunner.invalidate(\n\t\t\t\"This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\",\n\t\t);\n\t\tthis._disconnectFromAgent();\n\t\tthis._eventListeners = [];\n\t\tcleanupSessionResources(this.sessionId);\n\t}\n\n\t// =========================================================================\n\t// Read-only State Access\n\t// =========================================================================\n\n\t/** Full agent state */\n\tget state(): AgentState {\n\t\treturn this.agent.state;\n\t}\n\n\t/** Current model (may be undefined if not yet selected) */\n\tget model(): Model<any> | undefined {\n\t\treturn this.agent.state.model;\n\t}\n\n\t/** Current thinking level */\n\tget thinkingLevel(): ThinkingLevel {\n\t\treturn this.agent.state.thinkingLevel;\n\t}\n\n\t/** Whether the session is currently processing an agent run or post-run continuation. */\n\tget isStreaming(): boolean {\n\t\treturn this._isAgentRunActive;\n\t}\n\n\t/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */\n\tget isIdle(): boolean {\n\t\treturn !this._isAgentRunActive;\n\t}\n\n\t/** Current effective system prompt (includes any per-turn extension modifications) */\n\tget systemPrompt(): string {\n\t\treturn this.agent.state.systemPrompt;\n\t}\n\n\t/** Current retry attempt (0 if not retrying) */\n\tget retryAttempt(): number {\n\t\treturn this._retryAttempt;\n\t}\n\n\t/**\n\t * Get the names of currently active tools.\n\t * Returns the names of tools currently set on the agent.\n\t */\n\tgetActiveToolNames(): string[] {\n\t\treturn this.agent.state.tools.map((t) => t.name);\n\t}\n\n\t/**\n\t * Get all configured tools with name, description, parameter schema, prompt guidelines, and source metadata.\n\t */\n\tgetAllTools(): ToolInfo[] {\n\t\treturn Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({\n\t\t\tname: definition.name,\n\t\t\tdescription: definition.description,\n\t\t\tparameters: definition.parameters,\n\t\t\tpromptGuidelines: definition.promptGuidelines,\n\t\t\tsourceInfo,\n\t\t}));\n\t}\n\n\tgetToolDefinition(name: string): ToolDefinition | undefined {\n\t\treturn this._toolDefinitions.get(name)?.definition;\n\t}\n\n\t/**\n\t * Set active tools by name.\n\t * Only tools in the registry can be enabled. Unknown tool names are ignored.\n\t * Also rebuilds the system prompt to reflect the new tool set.\n\t * Changes take effect on the next agent turn.\n\t */\n\tsetActiveToolsByName(toolNames: string[]): void {\n\t\tconst tools: AgentTool[] = [];\n\t\tconst validToolNames: string[] = [];\n\t\tfor (const name of toolNames) {\n\t\t\tconst tool = this._toolRegistry.get(name);\n\t\t\tif (tool) {\n\t\t\t\ttools.push(tool);\n\t\t\t\tvalidToolNames.push(name);\n\t\t\t}\n\t\t}\n\t\tthis.agent.state.tools = tools;\n\n\t\t// Rebuild base system prompt with new tool set\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/**\n\t * Rebuild the base system prompt (public) — used after settings changes\n\t * that affect prompt content (e.g. memory injection mode).\n\t */\n\trebuildSystemPrompt(): void {\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/** Whether compaction or branch summarization is currently running */\n\tget isCompacting(): boolean {\n\t\treturn (\n\t\t\tthis._autoCompactionAbortController !== undefined ||\n\t\t\tthis._compactionAbortController !== undefined ||\n\t\t\tthis._branchSummaryAbortController !== undefined\n\t\t);\n\t}\n\n\t/** All messages including custom types like BashExecutionMessage */\n\tget messages(): AgentMessage[] {\n\t\treturn this.agent.state.messages;\n\t}\n\n\t/** Current steering mode */\n\tget steeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.agent.steeringMode;\n\t}\n\n\t/** Current follow-up mode */\n\tget followUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.agent.followUpMode;\n\t}\n\n\t/** Current session file path, or undefined if sessions are disabled */\n\tget sessionFile(): string | undefined {\n\t\treturn this.sessionManager.getSessionFile();\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string {\n\t\treturn this.sessionManager.getSessionId();\n\t}\n\n\t/** Current session display name, if set */\n\tget sessionName(): string | undefined {\n\t\treturn this.sessionManager.getSessionName();\n\t}\n\n\t/** Scoped models for cycling (from --models flag) */\n\tget scopedModels(): ReadonlyArray<{ model: Model<any>; thinkingLevel?: ThinkingLevel }> {\n\t\treturn this._scopedModels;\n\t}\n\n\t/** Update scoped models for cycling */\n\tsetScopedModels(scopedModels: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>): void {\n\t\tthis._scopedModels = scopedModels;\n\t}\n\n\t/** File-based prompt templates */\n\tget promptTemplates(): ReadonlyArray<PromptTemplate> {\n\t\treturn this._resourceLoader.getPrompts().prompts;\n\t}\n\n\tprivate _normalizePromptSnippet(text: string | undefined): string | undefined {\n\t\tif (!text) return undefined;\n\t\tconst oneLine = text\n\t\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t\t.replace(/\\s+/g, \" \")\n\t\t\t.trim();\n\t\treturn oneLine.length > 0 ? oneLine : undefined;\n\t}\n\n\tprivate _normalizePromptGuidelines(guidelines: string[] | undefined): string[] {\n\t\tif (!guidelines || guidelines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst unique = new Set<string>();\n\t\tfor (const guideline of guidelines) {\n\t\t\tconst normalized = guideline.trim();\n\t\t\tif (normalized.length > 0) {\n\t\t\t\tunique.add(normalized);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(unique);\n\t}\n\n\tprivate _rebuildSystemPrompt(toolNames: string[]): string {\n\t\tconst validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));\n\t\tconst toolSnippets: Record<string, string> = {};\n\t\tconst promptGuidelines: string[] = [];\n\t\tfor (const name of validToolNames) {\n\t\t\tconst snippet = this._toolPromptSnippets.get(name);\n\t\t\tif (snippet) {\n\t\t\t\ttoolSnippets[name] = snippet;\n\t\t\t}\n\n\t\t\tconst toolGuidelines = this._toolPromptGuidelines.get(name);\n\t\t\tif (toolGuidelines) {\n\t\t\t\tpromptGuidelines.push(...toolGuidelines);\n\t\t\t}\n\t\t}\n\n\t\tconst loaderSystemPrompt = this._resourceLoader.getSystemPrompt();\n\t\tconst loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();\n\t\t// Ada Intelligence Engine: PERSONALIDAD SIEMPRE presente (sesión principal).\n\t\t// No depende de que el modelo lea la skill: el árbol de decisión y los\n\t\t// procedimientos van inline en el prompt, cada turno.\n\t\tlet enginePrompt: string | undefined;\n\t\tif (\n\t\t\t!this._isSubagentSession &&\n\t\t\t(this.settingsManager.getGoalEngineEnabled() || this.settingsManager.getWorkflowsEnabled())\n\t\t) {\n\t\t\tenginePrompt = buildEnginePersonalityBlock();\n\t\t}\n\t\tconst appendSystemPrompt = enginePrompt\n\t\t\t? [enginePrompt, ...(loaderAppendSystemPrompt.length > 0 ? [loaderAppendSystemPrompt.join(\"\\n\\n\")] : [])].join(\n\t\t\t\t\t\"\\n\\n\",\n\t\t\t\t)\n\t\t\t: loaderAppendSystemPrompt.length > 0\n\t\t\t\t? loaderAppendSystemPrompt.join(\"\\n\\n\")\n\t\t\t\t: undefined;\n\t\tconst loadedSkills = this._resourceLoader.getSkills().skills;\n\t\tconst loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;\n\n\t\tthis._baseSystemPromptOptions = {\n\t\t\tcwd: this._cwd,\n\t\t\tskills: loadedSkills,\n\t\t\tcontextFiles: loadedContextFiles,\n\t\t\tcustomPrompt: loaderSystemPrompt,\n\t\t\tappendSystemPrompt,\n\t\t\tselectedTools: validToolNames,\n\t\t\ttoolSnippets,\n\t\t\tpromptGuidelines,\n\t\t};\n\t\treturn buildSystemPrompt(this._baseSystemPromptOptions);\n\t}\n\n\t// =========================================================================\n\t// Prompting\n\t// =========================================================================\n\n\tprivate async _runAgentPrompt(messages: AgentMessage | AgentMessage[]): Promise<void> {\n\t\tthis._isAgentRunActive = true;\n\t\ttry {\n\t\t\tawait this.agent.prompt(messages);\n\t\t\twhile (await this._handlePostAgentRun()) {\n\t\t\t\tawait this.agent.continue();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._systemPromptOverride = undefined;\n\t\t\tthis._flushPendingBashMessages();\n\t\t\tawait this._emitAgentSettled();\n\t\t}\n\t}\n\n\tprivate async _handlePostAgentRun(): Promise<boolean> {\n\t\tconst msg = this._lastAssistantMessage;\n\t\tthis._lastAssistantMessage = undefined;\n\t\tif (!msg) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (msg.stopReason === \"error\" && this._retryAttempt > 0) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt: this._retryAttempt,\n\t\t\t\tfinalError: msg.errorMessage,\n\t\t\t});\n\t\t\tthis._retryAttempt = 0;\n\t\t}\n\n\t\tif (await this._checkCompaction(msg)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// The agent loop drains both queues before emitting agent_end. Any messages\n\t\t// here were queued by agent_end extension handlers and need a continuation.\n\t\treturn this.agent.hasQueuedMessages();\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t * - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming\n\t * - Expands file-based prompt templates by default\n\t * - During streaming, queues via steer() or followUp() based on streamingBehavior option\n\t * - Validates model and API key before sending (when not streaming)\n\t * @throws Error if streaming and no streamingBehavior specified\n\t * @throws Error if no model selected or no API key available (when not streaming)\n\t */\n\tasync prompt(text: string, options?: PromptOptions): Promise<void> {\n\t\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\t\tconst preflightResult = options?.preflightResult;\n\t\tlet messages: AgentMessage[] | undefined;\n\n\t\ttry {\n\t\t\t// Handle extension commands first (execute immediately, even during streaming)\n\t\t\t// Extension commands manage their own LLM interaction via pi.sendMessage()\n\t\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\t\tconst handled = await this._tryExecuteExtensionCommand(text);\n\t\t\t\tif (handled) {\n\t\t\t\t\t// Extension command executed, no prompt to send\n\t\t\t\t\tpreflightResult?.(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this._compactionAbortController !== undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\t\tlet currentText = text;\n\t\t\tlet currentImages = options?.images;\n\t\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\t\tcurrentText,\n\t\t\t\t\tcurrentImages,\n\t\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t\t);\n\t\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\t\tpreflightResult?.(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\t\tlet expandedText = currentText;\n\t\t\tif (expandPromptTemplates) {\n\t\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t\t}\n\n\t\t\t// Enterprise security: detect sensitive data in the raw text (warning\n\t\t\t// + audit) and redact it BEFORE it reaches the LLM, the session file\n\t\t\t// or the chat transcript. The user sees censored content; the raw\n\t\t\t// secret is never persisted anywhere.\n\t\t\t// The engine exists whenever the SDK is built with the memory module;\n\t\t\t// detection + redaction only need config (not the initialized stores),\n\t\t\t// so they are ALWAYS applied — a failed init must never disable them.\n\t\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\t\tif (memoryEngine) {\n\t\t\t\tmemoryEngine.checkUserTextForSensitive(expandedText);\n\t\t\t\texpandedText = redactSensitive(expandedText, memoryEngine.privacyLevel);\n\t\t\t}\n\n\t\t\t// Secure Vault: expand @vault:<name> references so the LLM can use\n\t\t\t// secrets without them ever being persisted. The redacted text is\n\t\t\t// kept for session persistence (applied in message_end).\n\t\t\tconst vaultManager = this._resourceLoader.getVaultManager();\n\t\t\tif (vaultManager) {\n\t\t\t\tconst expansion = expandVaultRefs(expandedText, vaultManager);\n\t\t\t\tif (expansion.expanded.length > 0) {\n\t\t\t\t\tthis._pendingVaultExpansion = {\n\t\t\t\t\t\tid: `usr-vault-${++this._userMessageSeq}`,\n\t\t\t\t\t\tredactedText: expandedText,\n\t\t\t\t\t};\n\t\t\t\t\texpandedText = expansion.text;\n\t\t\t\t\t// Audit the vault usage (names only — never the values).\n\t\t\t\t\tmemoryEngine?.recordAudit({\n\t\t\t\t\t\taction: \"vault-use\",\n\t\t\t\t\t\tsource: \"user\",\n\t\t\t\t\t\ttarget: \"vault\",\n\t\t\t\t\t\tscope: vaultManager.status().vaultPath,\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\treason: expansion.expanded.join(\", \"),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If streaming, queue via steer() or followUp() based on option\n\t\t\tif (this.isStreaming) {\n\t\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t\t} else {\n\t\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t\t}\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Flush any pending bash messages before the new prompt\n\t\t\tthis._flushPendingBashMessages();\n\n\t\t\t// Validate model\n\t\t\tif (!this.model) {\n\t\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t\t}\n\n\t\t\tconst hasConfiguredAuth =\n\t\t\t\tthis._modelRuntime.hasConfiguredAuth(this.model.provider) ||\n\t\t\t\t(await this._modelRuntime.checkAuth(this.model.provider)) !== undefined;\n\t\t\tif (!hasConfiguredAuth) {\n\t\t\t\tconst isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);\n\t\t\t\tif (isOAuth) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t\t}\n\n\t\t\t// Check if we need to compact before sending (catches aborted responses).\n\t\t\t// The user's new prompt is sent below, so do not call agent.continue() here.\n\t\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\t\tif (lastAssistant) {\n\t\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t\t}\n\n\t\t\t// Build messages array (custom message if any, then user message)\n\t\t\tmessages = [];\n\n\t\t\t// Add user message\n\t\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\t\tif (currentImages) {\n\t\t\t\tuserContent.push(...currentImages);\n\t\t\t}\n\t\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\t\tthis._pendingVaultExpansion = null;\n\t\t\tconst userMessage: AgentMessage = {\n\t\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: userContent,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as AgentMessage;\n\t\t\tif (vaultExpansion) {\n\t\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t\t}\n\t\t\tmessages.push(userMessage);\n\n\t\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\t\tmessages.push(msg);\n\t\t\t}\n\t\t\tthis._pendingNextTurnMessages = [];\n\n\t\t\t// Emit before_agent_start extension event\n\t\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\t\texpandedText,\n\t\t\t\tcurrentImages,\n\t\t\t\tthis._baseSystemPrompt,\n\t\t\t\tthis._baseSystemPromptOptions,\n\t\t\t);\n\t\t\t// Add all custom messages from extensions\n\t\t\tif (result?.messages) {\n\t\t\t\tfor (const msg of result.messages) {\n\t\t\t\t\tmessages.push({\n\t\t\t\t\t\trole: \"custom\",\n\t\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\t\t// Untyped extensions can pass null/missing content; normalize at ingestion.\n\t\t\t\t\t\tcontent: msg.content ?? [],\n\t\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Apply extension-modified system prompt, or reset to base\n\t\t\tif (result?.systemPrompt !== undefined) {\n\t\t\t\tthis._systemPromptOverride = result.systemPrompt;\n\t\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t\t} else {\n\t\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\t\tthis._systemPromptOverride = undefined;\n\t\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tpreflightResult?.(false);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (!messages) {\n\t\t\treturn;\n\t\t}\n\n\t\tpreflightResult?.(true);\n\t\tawait this._runAgentPrompt(messages);\n\t}\n\n\t/**\n\t * Try to execute an extension command. Returns true if command was found and executed.\n\t */\n\tprivate async _tryExecuteExtensionCommand(text: string): Promise<boolean> {\n\t\t// Parse command name and args\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\t\tif (!command) return false;\n\n\t\t// Get command context from extension runner (includes session control methods)\n\t\tconst ctx = this._extensionRunner.createCommandContext();\n\n\t\ttry {\n\t\t\tawait command.handler(args, ctx);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Emit error via extension runner\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: `command:${commandName}`,\n\t\t\t\tevent: \"command\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Expand skill commands (/skill:name args) to their full content.\n\t * Returns the expanded text, or the original text if not a skill command or skill not found.\n\t * Emits errors via extension runner if file read fails.\n\t */\n\tprivate _expandSkillCommand(text: string): string {\n\t\tif (!text.startsWith(\"/skill:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\t\tif (!skill) return text; // Unknown skill, pass through\n\n\t\ttry {\n\t\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t\t} catch (err) {\n\t\t\t// Emit error like extension commands do\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: skill.filePath,\n\t\t\t\tevent: \"skill_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text; // Return original on error\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message while the agent is running.\n\t * Delivered after the current assistant turn finishes executing its tool calls,\n\t * before the next LLM call.\n\t * Expands skill commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync steer(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\n\t\t// Expand skill commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueSteer(expandedText, images);\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t * Delivered only when agent has no more tool calls or steering messages.\n\t * Expands skill commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync followUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\n\t\t// Expand skill commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueFollowUp(expandedText, images);\n\t}\n\n\t/**\n\t * Internal: Queue a steering message (already expanded, no extension command check).\n\t */\n\tprivate async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {\n\t\tthis._steeringMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\tthis._pendingVaultExpansion = null;\n\t\tconst message: AgentMessage = {\n\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t} as AgentMessage;\n\t\tif (vaultExpansion) {\n\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t}\n\t\tthis.agent.steer(message);\n\t}\n\n\t/**\n\t * Internal: Queue a follow-up message (already expanded, no extension command check).\n\t */\n\tprivate async _queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\tthis._followUpMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\tthis._pendingVaultExpansion = null;\n\t\tconst message: AgentMessage = {\n\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t} as AgentMessage;\n\t\tif (vaultExpansion) {\n\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t}\n\t\tthis.agent.followUp(message);\n\t\treturn;\n\t}\n\n\t/**\n\t * Throw an error if the text is an extension command.\n\t */\n\tprivate _throwIfExtensionCommand(text: string): void {\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\n\t\tif (command) {\n\t\t\tthrow new Error(\n\t\t\t\t`Extension command \"/${commandName}\" cannot be queued. Use prompt() or execute the command when not streaming.`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry.\n\t *\n\t * Handles three cases:\n\t * - Streaming: queues message, processed when loop pulls from queue\n\t * - Not streaming + triggerTurn: appends to state/session, starts new turn\n\t * - Not streaming + no trigger: appends to state/session, no turn\n\t *\n\t * @param message Custom message with customType, content, display, details\n\t * @param options.triggerTurn If true and not streaming, triggers a new LLM turn\n\t * @param options.deliverAs Delivery mode: \"steer\", \"followUp\", or \"nextTurn\"\n\t */\n\tasync sendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" | \"nextTurn\" },\n\t): Promise<void> {\n\t\tconst appMessage = {\n\t\t\trole: \"custom\" as const,\n\t\t\tcustomType: message.customType,\n\t\t\t// Untyped extensions can pass null/missing content; normalize at ingestion.\n\t\t\tcontent: message.content ?? [],\n\t\t\tdisplay: message.display,\n\t\t\tdetails: message.details,\n\t\t\ttimestamp: Date.now(),\n\t\t} satisfies CustomMessage<T>;\n\t\tif (options?.deliverAs === \"nextTurn\") {\n\t\t\tthis._pendingNextTurnMessages.push(appMessage);\n\t\t} else if (this.isStreaming && options?.triggerTurn !== false) {\n\t\t\tif (options?.deliverAs === \"followUp\") {\n\t\t\t\tthis.agent.followUp(appMessage);\n\t\t\t} else {\n\t\t\t\tthis.agent.steer(appMessage);\n\t\t\t}\n\t\t} else if (options?.triggerTurn) {\n\t\t\tawait this._runAgentPrompt(appMessage);\n\t\t} else {\n\t\t\tthis.agent.state.messages.push(appMessage);\n\t\t\tthis.sessionManager.appendCustomMessageEntry(\n\t\t\t\tmessage.customType,\n\t\t\t\tmessage.content,\n\t\t\t\tmessage.display,\n\t\t\t\tmessage.details,\n\t\t\t);\n\t\t\tthis._emit({ type: \"message_start\", message: appMessage });\n\t\t\tthis._emit({ type: \"message_end\", message: appMessage });\n\t\t}\n\t}\n\n\t/**\n\t * Send a user message to the agent. Always triggers a turn.\n\t * When the agent is streaming, use deliverAs to specify how to queue the message.\n\t *\n\t * @param content User message content (string or content array)\n\t * @param options.deliverAs Delivery mode when streaming: \"steer\" or \"followUp\"\n\t * @param options.expandPromptTemplates Whether to dispatch extension commands and expand skill commands and prompt templates. Default: false.\n\t */\n\tasync sendUserMessage(\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\toptions?: { deliverAs?: \"steer\" | \"followUp\"; expandPromptTemplates?: boolean },\n\t): Promise<void> {\n\t\t// Normalize content to text string + optional images\n\t\tlet text: string;\n\t\tlet images: ImageContent[] | undefined;\n\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else {\n\t\t\tconst textParts: string[] = [];\n\t\t\timages = [];\n\t\t\tfor (const part of content) {\n\t\t\t\tif (part.type === \"text\") {\n\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t} else {\n\t\t\t\t\timages.push(part);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttext = textParts.join(\"\\n\");\n\t\t\tif (images.length === 0) images = undefined;\n\t\t}\n\n\t\t// ── Optimizador autónomo de prompt ───────────────────────────────────\n\t\t// Mejora la petición del usuario (claridad, contexto, formato, criterios,\n\t\t// alcance) ANTES de que llegue al modelo. Fail-open: cualquier error o\n\t\t// timeout usa el prompt original — nunca degrada la experiencia.\n\t\tlet finalText = text;\n\t\tif (this._promptOptimizerMode !== \"off\" && typeof text === \"string\" && !text.startsWith(\"/\")) {\n\t\t\ttry {\n\t\t\t\tconst result = await this._runPromptOptimizer(text);\n\t\t\t\tif (result) {\n\t\t\t\t\tif (result.decision === \"clarify\" && this._promptOptimizerMode === \"clarify\") {\n\t\t\t\t\t\t// Preguntar primero y esperar respuesta; no enviamos aún.\n\t\t\t\t\t\tthis._pendingClarification = { original: text, questions: result.questions };\n\t\t\t\t\t\tthis._emit({\n\t\t\t\t\t\t\ttype: \"prompt_optimization\",\n\t\t\t\t\t\t\tdecision: \"clarify\",\n\t\t\t\t\t\t\toriginal: text,\n\t\t\t\t\t\t\tquestions: result.questions,\n\t\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst questions = result.questions.map((q, i) => `${i + 1}. ${q}`).join(\"\\n\");\n\t\t\t\t\t\tawait this.sendCustomMessage<{ q: string[] }>(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcustomType: \"clarify-questions\",\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `🔎 Para dejar tu petición sin huecos, necesito aclarar:\\n${questions}\\n\\nResponde a las preguntas y continuaré.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\t\tdetails: { q: result.questions },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ triggerTurn: false },\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (result.enhanced && result.enhanced !== text) {\n\t\t\t\t\t\tfinalText = result.enhanced;\n\t\t\t\t\t\tthis._emit({\n\t\t\t\t\t\t\ttype: \"prompt_optimization\",\n\t\t\t\t\t\t\tdecision: \"enhance\",\n\t\t\t\t\t\t\toriginal: text,\n\t\t\t\t\t\t\tenhanced: result.enhanced,\n\t\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Mensaje persistente en el chat (CLI + desktop): muestra el\n\t\t\t\t\t\t// antes → después para que el usuario vea la mejora.\n\t\t\t\t\t\tvoid this.sendCustomMessage<{ original: string; summary: string }>(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcustomType: \"prompt-optimization\",\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: result.enhanced }],\n\t\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\t\tdetails: { original: text, summary: result.summary },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ triggerTurn: false },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fail-open: continuamos con el prompt original\n\t\t\t}\n\t\t}\n\n\t\tawait this.prompt(finalText, {\n\t\t\texpandPromptTemplates: options?.expandPromptTemplates ?? false,\n\t\t\tstreamingBehavior: options?.deliverAs,\n\t\t\timages,\n\t\t\tsource: \"extension\",\n\t\t});\n\t}\n\n\t/**\n\t * Ejecuta el optimizador con el modelo de la sesión. Timeout de 12s,\n\t * nunca lanza (devuelve undefined → fail-open).\n\t */\n\tprivate async _runPromptOptimizer(\n\t\ttext: string,\n\t): Promise<\n\t\t| { decision: \"clarify\"; questions: string[]; enhanced: string; summary: string }\n\t\t| { decision: \"enhance\"; questions: string[]; enhanced: string; summary: string }\n\t\t| undefined\n\t> {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\t\ttry {\n\t\t\tconst answers = this._pendingClarification?.answers;\n\t\t\tconst result = await Promise.race([\n\t\t\t\toptimizeUserPrompt(\n\t\t\t\t\t{\n\t\t\t\t\t\ttext,\n\t\t\t\t\t\t// En modo auto nunca bloqueamos al usuario: si el LLM pide\n\t\t\t\t\t\t// clarificar, forzamos una mejora con supuestos razonables.\n\t\t\t\t\t\tforceEnhance: this._promptOptimizerMode === \"auto\",\n\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\tprojectDir: this._cwd,\n\t\t\t\t\t\t\t// Si el usuario ya respondió a las preguntas de la ronda\n\t\t\t\t\t\t\t// anterior, se las pasamos al optimizador como contexto.\n\t\t\t\t\t\t\trecentMessages: answers?.length\n\t\t\t\t\t\t\t\t? [...answers, ...(this._recentUserPrompts(3) ?? [])]\n\t\t\t\t\t\t\t\t: this._recentUserPrompts(3),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tasync (systemPrompt, userContent) => {\n\t\t\t\t\t\tconst completed = await this._modelRuntime.completeSimple(model, {\n\t\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\t\tmessages: [{ role: \"user\", content: userContent }],\n\t\t\t\t\t\t\tsignal: AbortSignal.timeout(20_000),\n\t\t\t\t\t\t} as never);\n\t\t\t\t\t\treturn contentText(completed.content, \"\").trim();\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tnew Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), 25_000)),\n\t\t\t]);\n\t\t\tif (!result) return undefined;\n\t\t\tthis._pendingClarification = undefined;\n\t\t\treturn result;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/** Últimos prompts de usuario (para dar contexto al optimizador). */\n\tprivate _recentUserPrompts(max: number): string[] {\n\t\tconst out: string[] = [];\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0 && out.length < max; i--) {\n\t\t\tconst m = messages[i];\n\t\t\tif (m.role === \"user\") {\n\t\t\t\tconst t = contentText(m.content, \"\");\n\t\t\t\tif (t.trim()) out.unshift(t.slice(0, 500));\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Ajusta el modo del optimizador autónomo de prompts. */\n\tsetPromptOptimizerMode(mode: \"off\" | \"auto\" | \"clarify\"): void {\n\t\tthis._promptOptimizerMode = mode;\n\t}\n\n\t/** Modo actual del optimizador autónomo de prompts. */\n\tgetPromptOptimizerMode(): \"off\" | \"auto\" | \"clarify\" {\n\t\treturn this._promptOptimizerMode;\n\t}\n\n\t/**\n\t * Clear all queued messages and return them.\n\t * Useful for restoring to editor when user aborts.\n\t * @returns Object with steering and followUp arrays\n\t */\n\tclearQueue(): { steering: string[]; followUp: string[] } {\n\t\tconst steering = [...this._steeringMessages];\n\t\tconst followUp = [...this._followUpMessages];\n\t\tthis._steeringMessages = [];\n\t\tthis._followUpMessages = [];\n\t\tthis.agent.clearAllQueues();\n\t\tthis._emitQueueUpdate();\n\t\treturn { steering, followUp };\n\t}\n\n\t/** Number of pending messages (includes both steering and follow-up) */\n\tget pendingMessageCount(): number {\n\t\treturn this._steeringMessages.length + this._followUpMessages.length;\n\t}\n\n\t/** Get pending steering messages (read-only) */\n\tgetSteeringMessages(): readonly string[] {\n\t\treturn this._steeringMessages;\n\t}\n\n\t/** Get pending follow-up messages (read-only) */\n\tgetFollowUpMessages(): readonly string[] {\n\t\treturn this._followUpMessages;\n\t}\n\n\tget resourceLoader(): ResourceLoader {\n\t\treturn this._resourceLoader;\n\t}\n\n\t/**\n\t * Abort current operation and wait for agent to become idle.\n\t */\n\tasync abort(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tawait this.waitForIdle();\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\tif (this.isIdle) {\n\t\t\treturn;\n\t\t}\n\t\tawait this._getIdleWaitPromise();\n\t}\n\n\t// =========================================================================\n\t// Model Management\n\t// =========================================================================\n\n\tprivate async _emitModelSelect(\n\t\tnextModel: Model<any>,\n\t\tpreviousModel: Model<any> | undefined,\n\t\tsource: \"set\" | \"cycle\" | \"restore\",\n\t): Promise<void> {\n\t\tif (modelsAreEqual(previousModel, nextModel)) return;\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"model_select\",\n\t\t\tmodel: nextModel,\n\t\t\tpreviousModel,\n\t\t\tsource,\n\t\t});\n\t}\n\n\t/**\n\t * Set model directly.\n\t * Validates that auth is configured and saves to the session transcript.\n\t * Persists to global defaults only when options.persist is true.\n\t * @throws Error if no auth is configured for the model\n\t */\n\tasync setModel(model: Model<any>, options: ModelMutationOptions = {}): Promise<void> {\n\t\tif (!(await this._modelRuntime.checkAuth(model.provider))) {\n\t\t\tthrow new Error(`No API key for ${model.provider}/${model.id}`);\n\t\t}\n\n\t\tconst previousModel = this.model;\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(model);\n\t\tthis.agent.state.model = model;\n\t\tthis.sessionManager.appendModelChange(model.provider, model.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\t}\n\n\t\t// Re-clamp session thinking level for new model's capabilities.\n\t\t// Per-model thinking level overrides take priority over session carry-over.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(model, previousModel, \"set\");\n\t}\n\n\t/**\n\t * Cycle to next/previous model.\n\t * Uses scoped models (from --models flag) if available, otherwise all available models.\n\t * @param direction - \"forward\" (default) or \"backward\"\n\t * @returns The new model info, or undefined if only one model available\n\t */\n\tasync cycleModel(\n\t\tdirection: \"forward\" | \"backward\" = \"forward\",\n\t\toptions: ModelMutationOptions = {},\n\t): Promise<ModelCycleResult | undefined> {\n\t\tif (this._scopedModels.length > 0) {\n\t\t\treturn this._cycleScopedModel(direction, options);\n\t\t}\n\t\treturn this._cycleAvailableModel(direction, options);\n\t}\n\n\tprivate async _cycleScopedModel(\n\t\tdirection: \"forward\" | \"backward\",\n\t\toptions: ModelMutationOptions,\n\t): Promise<ModelCycleResult | undefined> {\n\t\tconst availableIds = new Set(\n\t\t\tthis._modelRuntime.getAvailableSnapshot().map((model) => `${model.provider}\\0${model.id}`),\n\t\t);\n\t\tconst scopedModels = this._scopedModels.filter((scoped) =>\n\t\t\tavailableIds.has(`${scoped.model.provider}\\0${scoped.model.id}`),\n\t\t);\n\t\tif (scopedModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = scopedModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst next = scopedModels[nextIndex];\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);\n\n\t\t// Apply model\n\t\tthis.agent.state.model = next.model;\n\t\tthis.sessionManager.appendModelChange(next.model.provider, next.model.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);\n\t\t}\n\n\t\t// Apply session thinking level.\n\t\t// - Explicit scoped model thinking level overrides current session level\n\t\t// - Undefined scoped model thinking level inherits the current session preference\n\t\t// setThinkingLevel clamps to model capabilities.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(next.model, currentModel, \"cycle\");\n\n\t\treturn { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };\n\t}\n\n\tprivate async _cycleAvailableModel(\n\t\tdirection: \"forward\" | \"backward\",\n\t\toptions: ModelMutationOptions,\n\t): Promise<ModelCycleResult | undefined> {\n\t\tconst availableModels = this._modelRuntime.getAvailableSnapshot();\n\t\tif (availableModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = availableModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst nextModel = availableModels[nextIndex];\n\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);\n\t\tthis.agent.state.model = nextModel;\n\t\tthis.sessionManager.appendModelChange(nextModel.provider, nextModel.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);\n\t\t}\n\n\t\t// Re-clamp session thinking level for new model's capabilities.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(nextModel, currentModel, \"cycle\");\n\n\t\treturn { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };\n\t}\n\n\t// =========================================================================\n\t// Thinking Level Management\n\t// =========================================================================\n\n\t/**\n\t * Set thinking level.\n\t * Clamps to model capabilities based on available thinking levels.\n\t * Saves the clamped level to the session transcript only if the level actually changes.\n\t * Persists the requested level to global defaults only when options.persist is true.\n\t */\n\tsetThinkingLevel(level: ThinkingLevel, options: ModelMutationOptions = {}): void {\n\t\tconst availableLevels = this.getAvailableThinkingLevels();\n\t\tconst effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);\n\n\t\t// Only persist if actually changing\n\t\tconst previousLevel = this.agent.state.thinkingLevel;\n\t\tconst isChanging = effectiveLevel !== previousLevel;\n\n\t\tthis.agent.state.thinkingLevel = effectiveLevel;\n\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultThinkingLevel(level);\n\t\t}\n\n\t\tif (isChanging) {\n\t\t\tthis.sessionManager.appendThinkingLevelChange(effectiveLevel);\n\t\t\tthis._emit({ type: \"thinking_level_changed\", level: effectiveLevel });\n\t\t\tvoid this._extensionRunner.emit({\n\t\t\t\ttype: \"thinking_level_select\",\n\t\t\t\tlevel: effectiveLevel,\n\t\t\t\tpreviousLevel,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Cycle to next thinking level.\n\t * @returns New level, or undefined if model doesn't support thinking\n\t */\n\tcycleThinkingLevel(options: ModelMutationOptions = {}): ThinkingLevel | undefined {\n\t\tif (!this.supportsThinking()) return undefined;\n\n\t\tconst levels = this.getAvailableThinkingLevels();\n\t\tconst currentIndex = levels.indexOf(this.thinkingLevel);\n\t\tconst nextIndex = (currentIndex + 1) % levels.length;\n\t\tconst nextLevel = levels[nextIndex];\n\n\t\tthis.setThinkingLevel(nextLevel, options);\n\t\treturn nextLevel;\n\t}\n\n\t/**\n\t * Get available thinking levels for current model.\n\t * The provider will clamp to what the specific model supports internally.\n\t */\n\tgetAvailableThinkingLevels(): ThinkingLevel[] {\n\t\tif (!this.model) return [...THINKING_LEVEL_OPTIONS];\n\t\treturn getSupportedThinkingLevels(this.model) as ThinkingLevel[];\n\t}\n\n\t/**\n\t * Check if current model supports thinking/reasoning.\n\t */\n\tsupportsThinking(): boolean {\n\t\treturn !!this.model?.reasoning;\n\t}\n\n\tprivate _getThinkingLevelForModelSwitch(targetModel?: Model<any>, explicitLevel?: ThinkingLevel): ThinkingLevel {\n\t\tif (explicitLevel !== undefined) {\n\t\t\treturn explicitLevel;\n\t\t}\n\t\t// Per-model default takes priority when switching to a model that has one\n\t\tif (targetModel) {\n\t\t\tconst perModel = this.settingsManager.getModelThinkingLevel(targetModel.provider, targetModel.id);\n\t\t\tif (perModel !== undefined) {\n\t\t\t\treturn perModel;\n\t\t\t}\n\t\t}\n\t\tif (!this.supportsThinking()) {\n\t\t\treturn this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t\t}\n\t\treturn this.thinkingLevel;\n\t}\n\n\tprivate _clampThinkingLevel(level: ThinkingLevel, _availableLevels: ThinkingLevel[]): ThinkingLevel {\n\t\treturn this.model ? (clampThinkingLevel(this.model, level) as ThinkingLevel) : \"off\";\n\t}\n\n\t// =========================================================================\n\t// Queue Mode Management\n\t// =========================================================================\n\n\tprivate syncQueueModesFromSettings(): void {\n\t\tthis.agent.steeringMode = this.settingsManager.getSteeringMode();\n\t\tthis.agent.followUpMode = this.settingsManager.getFollowUpMode();\n\t}\n\n\t/**\n\t * Set steering message mode.\n\t * Saves to settings.\n\t */\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.agent.steeringMode = mode;\n\t\tthis.settingsManager.setSteeringMode(mode);\n\t}\n\n\t/**\n\t * Set follow-up message mode.\n\t * Saves to settings.\n\t */\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.agent.followUpMode = mode;\n\t\tthis.settingsManager.setFollowUpMode(mode);\n\t}\n\n\t// =========================================================================\n\t// Compaction\n\t// =========================================================================\n\n\t/** Generate Pi's built-in compaction summary for manual and automatic compaction. */\n\tprivate async _runDefaultCompaction(\n\t\tpreparation: CompactionPreparation,\n\t\trequestModel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tcustomInstructions: string | undefined,\n\t\tsignal: AbortSignal,\n\t\tenv: Record<string, string> | undefined,\n\t\treason: \"manual\" | \"threshold\" | \"overflow\",\n\t): Promise<CompactionResult> {\n\t\treturn compact(\n\t\t\tpreparation,\n\t\t\trequestModel,\n\t\t\tapiKey,\n\t\t\theaders,\n\t\t\tcustomInstructions,\n\t\t\tsignal,\n\t\t\tthis.thinkingLevel,\n\t\t\tthis.agent.streamFunction,\n\t\t\tenv,\n\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason }),\n\t\t\tundefined, // sessionId\n\t\t);\n\t}\n\n\t/**\n\t * Manually compact the session context.\n\t *\n\t * This is the manual entry point used by `/compact`, RPC, and extensions. It is\n\t * separate from automatic threshold/overflow compaction, which enters through\n\t * `_checkCompaction()` and `_runAutoCompaction()`. After preparation and the\n\t * `session_before_compact` hook, both paths call the lower-level `compact()`\n\t * function imported from `./compaction/index.ts`, unless the hook cancels or\n\t * supplies a custom result.\n\t *\n\t * Aborts the current agent operation first. Manual compaction never retries or\n\t * continues the interrupted agent turn.\n\t *\n\t * @param customInstructions Optional instructions for the compaction summary\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tawait this.abort();\n\t\tthis._compactionAbortController = new AbortController();\n\t\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\t\tlet fromExtension = false;\n\n\t\ttry {\n\t\t\tif (!this.model) {\n\t\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t\t}\n\n\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);\n\n\t\t\tconst pathEntries = this.sessionManager.getBranch();\n\t\t\tconst settings = this.settingsManager.getCompactionSettings();\n\n\t\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\t\tif (!preparation) {\n\t\t\t\t// Check why we can't compact\n\t\t\t\tconst lastEntry = pathEntries[pathEntries.length - 1];\n\t\t\t\tif (lastEntry?.type === \"compaction\") {\n\t\t\t\t\tthrow new Error(\"Already compacted\");\n\t\t\t\t}\n\t\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t\t}\n\n\t\t\tlet extensionCompaction: CompactionResult | undefined;\n\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t\t\tconst result = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_compact\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tbranchEntries: pathEntries,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treason: \"manual\",\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tsignal: this._compactionAbortController.signal,\n\t\t\t\t})) as SessionBeforeCompactResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t\t\t}\n\n\t\t\t\tif (result?.compaction) {\n\t\t\t\t\textensionCompaction = result.compaction;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet summary: string;\n\t\t\tlet firstKeptEntryId: string;\n\t\t\tlet tokensBefore: number;\n\t\t\tlet usage: Usage | undefined;\n\t\t\tlet details: unknown;\n\n\t\t\tif (extensionCompaction) {\n\t\t\t\t// Extension provided compaction content\n\t\t\t\tsummary = extensionCompaction.summary;\n\t\t\t\tfirstKeptEntryId = extensionCompaction.firstKeptEntryId;\n\t\t\t\ttokensBefore = extensionCompaction.tokensBefore;\n\t\t\t\tusage = extensionCompaction.usage;\n\t\t\t\tdetails = extensionCompaction.details;\n\t\t\t} else {\n\t\t\t\t// Shared default summary generator, also used by automatic compaction.\n\t\t\t\tconst result = await this._runDefaultCompaction(\n\t\t\t\t\tpreparation,\n\t\t\t\t\trequestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\tthis._compactionAbortController.signal,\n\t\t\t\t\tenv,\n\t\t\t\t\t\"manual\",\n\t\t\t\t);\n\t\t\t\tsummary = result.summary;\n\t\t\t\tfirstKeptEntryId = result.firstKeptEntryId;\n\t\t\t\ttokensBefore = result.tokensBefore;\n\t\t\t\tusage = result.usage;\n\t\t\t\tdetails = result.details;\n\t\t\t}\n\n\t\t\tif (this._compactionAbortController.signal.aborted) {\n\t\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t\t}\n\n\t\t\tthis.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);\n\t\t\tconst newEntries = this.sessionManager.getEntries();\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\t\t\tconst estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);\n\n\t\t\t// Get the saved compaction entry for the extension event\n\t\t\tconst savedCompactionEntry = newEntries.find((e) => e.type === \"compaction\" && e.summary === summary) as\n\t\t\t\t| CompactionEntry\n\t\t\t\t| undefined;\n\n\t\t\tif (this._extensionRunner && savedCompactionEntry) {\n\t\t\t\tawait this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_compact\",\n\t\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\treason: \"manual\",\n\t\t\t\t\twillRetry: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst compactionResult: CompactionResult = {\n\t\t\t\tsummary,\n\t\t\t\tfirstKeptEntryId,\n\t\t\t\ttokensBefore,\n\t\t\t\testimatedTokensAfter,\n\t\t\t\tusage,\n\t\t\t\tdetails,\n\t\t\t};\n\t\t\t// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: compactionResult,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn compactionResult;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\t\tconst errorMessage = aborted ? undefined : `Compaction failed: ${message}`;\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage,\n\t\t\t});\n\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\treason: \"manual\",\n\t\t\t\terrorMessage,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis._compactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Cancel in-progress compaction (manual or auto).\n\t */\n\tabortCompaction(): void {\n\t\tthis._compactionAbortController?.abort();\n\t\tthis._autoCompactionAbortController?.abort();\n\t}\n\n\t/**\n\t * Cancel in-progress branch summarization.\n\t */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\t/**\n\t * Dispatch automatic compaction after `agent_end` or before prompt submission.\n\t * Manual compaction does not call this method; it enters through `compact()`.\n\t *\n\t * Automatic cases:\n\t * 1. Overflow with retry: a context-overflow error or recoverable length stop;\n\t * remove the failed assistant message, compact, and retry the turn once.\n\t * 2. Overflow without retry: a successful response exceeded the configured\n\t * context window; compact but preserve the completed response.\n\t * 3. Threshold without retry: valid or estimated context usage crossed the\n\t * configured threshold; compact without retrying the completed response.\n\t *\n\t * Each case calls `_runAutoCompaction()`. After preparation and the\n\t * `session_before_compact` hook, that method calls the lower-level `compact()`\n\t * function imported from `./compaction/index.ts`, unless the hook cancels or\n\t * supplies a custom result.\n\t *\n\t * @param assistantMessage The assistant message to check\n\t * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n\t * @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages\n\t */\n\tprivate async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tif (!settings.enabled) return false;\n\n\t\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\t\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return false;\n\n\t\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t\t// Skip overflow check if the message came from a different model.\n\t\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t\t// shouldn't trigger compaction for the new model.\n\t\tconst sameModel =\n\t\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t\t// Skip compaction checks if this assistant message is older than the latest\n\t\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t\t// from retriggering compaction on the first prompt after compaction.\n\t\tconst compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());\n\t\tconst assistantIsFromBeforeCompaction =\n\t\t\tcompactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();\n\t\tif (assistantIsFromBeforeCompaction) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Automatic cases 1 and 2: context overflow.\n\t\t// A length stop is recoverable when output ended below the model's original desired limit,\n\t\t// independent of the configured context size or any context-clamped provider request limit.\n\t\tconst contextOverflow = sameModel && isContextOverflow(assistantMessage, contextWindow);\n\t\tconst recoverableLength = sameModel && isRecoverableLength(assistantMessage, this.model?.maxTokens ?? 0);\n\t\tif (contextOverflow || recoverableLength) {\n\t\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\n\t\t\t// Case 2: the response completed successfully. Compact, but do not retry because\n\t\t\t// agent.continue() cannot continue from a completed assistant response.\n\t\t\tif (!willRetry) {\n\t\t\t\treturn await this._runAutoCompaction(\"overflow\", false);\n\t\t\t}\n\n\t\t\tif (this._overflowRecoveryAttempted) {\n\t\t\t\tconst errorMessage = contextOverflow\n\t\t\t\t\t? \"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\"\n\t\t\t\t\t: \"Truncated response recovery failed after one compact-and-retry attempt.\";\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\terrorMessage,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension: false,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Case 1: remove the failed or truncated message from agent state, compact, and\n\t\t\t// retry once. The message remains in session history but is excluded from retry context.\n\t\t\tthis._overflowRecoveryAttempted = true;\n\t\t\tconst messages = this.agent.state.messages;\n\t\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t}\n\t\t\treturn await this._runAutoCompaction(\"overflow\", willRetry);\n\t\t}\n\n\t\t// Case 3: threshold compaction without retry.\n\t\t// For error messages or all-zero usage messages, estimate from the last valid response.\n\t\t// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage\n\t\t// responses can still compact and do not reset context accounting.\n\t\tlet contextTokens: number;\n\t\tconst directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;\n\t\tif (assistantMessage.stopReason === \"error\" || directContextTokens === 0) {\n\t\t\tconst messages = this.agent.state.messages;\n\t\t\tconst estimate = estimateContextTokens(messages);\n\t\t\t// Without provider usage, estimate.tokens is the pure message-size estimate.\n\t\t\t// Only usage-backed estimates need the stale pre-compaction check.\n\t\t\tif (estimate.lastUsageIndex !== null) {\n\t\t\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t\t\t// trigger compaction right after one just finished.\n\t\t\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\t\t\tif (\n\t\t\t\t\tcompactionEntry &&\n\t\t\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionEntry.timestamp).getTime()\n\t\t\t\t) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontextTokens = estimate.tokens;\n\t\t} else {\n\t\t\tcontextTokens = directContextTokens;\n\t\t}\n\t\tif (shouldCompact(contextTokens, contextWindow, settings)) {\n\t\t\treturn await this._runAutoCompaction(\"threshold\", false);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Execute threshold or overflow compaction. Manual compaction uses\n\t * `AgentSession.compact()` instead. Both paths call the lower-level `compact()`\n\t * function imported from `./compaction/index.ts` after preparation and extension\n\t * interception.\n\t *\n\t * @param reason Automatic trigger selected by `_checkCompaction()`\n\t * @param willRetry Whether to continue the interrupted turn after overflow compaction\n\t * @returns Whether the post-run loop should call `agent.continue()`\n\t */\n\tprivate async _runAutoCompaction(reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tlet started = false;\n\t\tlet fromExtension = false;\n\n\t\ttry {\n\t\t\tif (!this.model) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);\n\n\t\t\tconst pathEntries = this.sessionManager.getBranch();\n\n\t\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\t\tif (!preparation) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis._emit({ type: \"compaction_start\", reason });\n\t\t\tthis._autoCompactionAbortController = new AbortController();\n\t\t\tstarted = true;\n\n\t\t\tlet extensionCompaction: CompactionResult | undefined;\n\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t\t\tconst extensionResult = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_compact\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tbranchEntries: pathEntries,\n\t\t\t\t\tcustomInstructions: undefined,\n\t\t\t\t\treason,\n\t\t\t\t\twillRetry,\n\t\t\t\t\tsignal: this._autoCompactionAbortController.signal,\n\t\t\t\t})) as SessionBeforeCompactResult | undefined;\n\n\t\t\t\tif (extensionResult?.cancel) {\n\t\t\t\t\tthis._emit({\n\t\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\t\treason,\n\t\t\t\t\t\tresult: undefined,\n\t\t\t\t\t\taborted: true,\n\t\t\t\t\t\twillRetry: false,\n\t\t\t\t\t});\n\t\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\t\treason,\n\t\t\t\t\t\taborted: true,\n\t\t\t\t\t\twillRetry: false,\n\t\t\t\t\t\tfromExtension: false,\n\t\t\t\t\t});\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (extensionResult?.compaction) {\n\t\t\t\t\textensionCompaction = extensionResult.compaction;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet summary: string;\n\t\t\tlet firstKeptEntryId: string;\n\t\t\tlet tokensBefore: number;\n\t\t\tlet usage: Usage | undefined;\n\t\t\tlet details: unknown;\n\n\t\t\tif (extensionCompaction) {\n\t\t\t\t// Extension provided compaction content\n\t\t\t\tsummary = extensionCompaction.summary;\n\t\t\t\tfirstKeptEntryId = extensionCompaction.firstKeptEntryId;\n\t\t\t\ttokensBefore = extensionCompaction.tokensBefore;\n\t\t\t\tusage = extensionCompaction.usage;\n\t\t\t\tdetails = extensionCompaction.details;\n\t\t\t} else {\n\t\t\t\t// Shared default summary generator, also used by manual compaction.\n\t\t\t\tconst compactResult = await this._runDefaultCompaction(\n\t\t\t\t\tpreparation,\n\t\t\t\t\trequestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tundefined,\n\t\t\t\t\tthis._autoCompactionAbortController.signal,\n\t\t\t\t\tenv,\n\t\t\t\t\treason,\n\t\t\t\t);\n\t\t\t\tsummary = compactResult.summary;\n\t\t\t\tfirstKeptEntryId = compactResult.firstKeptEntryId;\n\t\t\t\ttokensBefore = compactResult.tokensBefore;\n\t\t\t\tusage = compactResult.usage;\n\t\t\t\tdetails = compactResult.details;\n\t\t\t}\n\n\t\t\tif (this._autoCompactionAbortController.signal.aborted) {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason,\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: true,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason,\n\t\t\t\t\taborted: true,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);\n\t\t\tconst newEntries = this.sessionManager.getEntries();\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\t\t\tconst estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);\n\n\t\t\t// Get the saved compaction entry for the extension event\n\t\t\tconst savedCompactionEntry = newEntries.find((e) => e.type === \"compaction\" && e.summary === summary) as\n\t\t\t\t| CompactionEntry\n\t\t\t\t| undefined;\n\n\t\t\tif (this._extensionRunner && savedCompactionEntry) {\n\t\t\t\tawait this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_compact\",\n\t\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\treason,\n\t\t\t\t\twillRetry,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst result: CompactionResult = {\n\t\t\t\tsummary,\n\t\t\t\tfirstKeptEntryId,\n\t\t\t\ttokensBefore,\n\t\t\t\testimatedTokensAfter,\n\t\t\t\tusage,\n\t\t\t\tdetails,\n\t\t\t};\n\t\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\n\t\t\tif (willRetry) {\n\t\t\t\tconst messages = this.agent.state.messages;\n\t\t\t\tconst lastMsg = messages[messages.length - 1];\n\t\t\t\t// The overflow response was persisted on message_end before _checkCompaction() removed it\n\t\t\t\t// from agent state. Rebuilding state from the new compaction can restore that kept entry,\n\t\t\t\t// leaving an assistant as the final message. agent.continue() rejects that state, so remove\n\t\t\t\t// the retriable error or truncated-length response again before continuing the interrupted turn.\n\t\t\t\tif (lastMsg?.role === \"assistant\" && (lastMsg.stopReason === \"error\" || lastMsg.stopReason === \"length\")) {\n\t\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Auto-compaction can complete while follow-up/steering/custom messages are waiting.\n\t\t\t// Continue once so queued messages are delivered.\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\t\tif (started) {\n\t\t\t\tconst formattedErrorMessage =\n\t\t\t\t\treason === \"overflow\"\n\t\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`;\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason,\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\terrorMessage: formattedErrorMessage,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason,\n\t\t\t\t\terrorMessage: formattedErrorMessage,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._autoCompactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Toggle auto-compaction setting.\n\t */\n\tsetAutoCompactionEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setCompactionEnabled(enabled);\n\t}\n\n\t/** Whether auto-compaction is enabled */\n\tget autoCompactionEnabled(): boolean {\n\t\treturn this.settingsManager.getCompactionEnabled();\n\t}\n\n\tasync bindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\tif (bindings.uiContext !== undefined) {\n\t\t\tthis._extensionUIContext = bindings.uiContext;\n\t\t}\n\t\tif (bindings.mode !== undefined) {\n\t\t\tthis._extensionMode = bindings.mode;\n\t\t}\n\t\tif (bindings.commandContextActions !== undefined) {\n\t\t\tthis._extensionCommandContextActions = bindings.commandContextActions;\n\t\t}\n\t\tif (bindings.abortHandler !== undefined) {\n\t\t\tthis._extensionAbortHandler = bindings.abortHandler;\n\t\t}\n\t\tif (bindings.shutdownHandler !== undefined) {\n\t\t\tthis._extensionShutdownHandler = bindings.shutdownHandler;\n\t\t}\n\t\tif (bindings.onError !== undefined) {\n\t\t\tthis._extensionErrorListener = bindings.onError;\n\t\t}\n\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\tawait this._extensionRunner.emit(this._sessionStartEvent);\n\t\tawait this.extendResourcesFromExtensions(this._sessionStartEvent.reason === \"reload\" ? \"reload\" : \"startup\");\n\t}\n\n\tprivate async extendResourcesFromExtensions(reason: \"startup\" | \"reload\"): Promise<void> {\n\t\tif (!this._extensionRunner.hasHandlers(\"resources_discover\")) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(\n\t\t\tthis._cwd,\n\t\t\treason,\n\t\t);\n\n\t\tif (skillPaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst extensionPaths: ResourceExtensionPaths = {\n\t\t\tskillPaths: this.buildExtensionResourcePaths(skillPaths),\n\t\t\tpromptPaths: this.buildExtensionResourcePaths(promptPaths),\n\t\t\tthemePaths: this.buildExtensionResourcePaths(themePaths),\n\t\t};\n\n\t\tthis._resourceLoader.extendResources(extensionPaths);\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t}\n\n\tprivate buildExtensionResourcePaths(entries: Array<{ path: string; extensionPath: string }>): Array<{\n\t\tpath: string;\n\t\tmetadata: { source: string; scope: \"temporary\"; origin: \"top-level\"; baseDir?: string };\n\t}> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst source = this.getExtensionSourceLabel(entry.extensionPath);\n\t\t\tconst baseDir = entry.extensionPath.startsWith(\"<\") ? undefined : dirname(entry.extensionPath);\n\t\t\treturn {\n\t\t\t\tpath: entry.path,\n\t\t\t\tmetadata: {\n\t\t\t\t\tsource,\n\t\t\t\t\tscope: \"temporary\",\n\t\t\t\t\torigin: \"top-level\",\n\t\t\t\t\tbaseDir,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getExtensionSourceLabel(extensionPath: string): string {\n\t\tif (extensionPath.startsWith(\"<\")) {\n\t\t\treturn `extension:${extensionPath.replace(/[<>]/g, \"\")}`;\n\t\t}\n\t\tconst base = basename(extensionPath);\n\t\tconst name = base.replace(/\\.(ts|js)$/, \"\");\n\t\treturn `extension:${name}`;\n\t}\n\n\tprivate _applyExtensionBindings(runner: ExtensionRunner): void {\n\t\trunner.setUIContext(this._extensionUIContext, this._extensionMode);\n\t\trunner.bindCommandContext(this._extensionCommandContextActions);\n\n\t\tthis._extensionErrorUnsubscriber?.();\n\t\tthis._extensionErrorUnsubscriber = this._extensionErrorListener\n\t\t\t? runner.onError(this._extensionErrorListener)\n\t\t\t: undefined;\n\t}\n\n\tprivate _refreshCurrentModelFromRegistry(): void {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst refreshedModel = this._modelRuntime.getModel(currentModel.provider, currentModel.id);\n\t\tif (!refreshedModel || refreshedModel === currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.agent.state.model = refreshedModel;\n\t}\n\n\tprivate _bindExtensionCore(runner: ExtensionRunner): void {\n\t\tconst getCommands = (): SlashCommandInfo[] => {\n\t\t\tconst extensionCommands: SlashCommandInfo[] = runner.getRegisteredCommands().map((command) => ({\n\t\t\t\tname: command.invocationName,\n\t\t\t\tdescription: command.description,\n\t\t\t\tsource: \"extension\",\n\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst templates: SlashCommandInfo[] = this.promptTemplates.map((template) => ({\n\t\t\t\tname: template.name,\n\t\t\t\tdescription: template.description,\n\t\t\t\tsource: \"prompt\",\n\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst skills: SlashCommandInfo[] = this._resourceLoader.getSkills().skills.map((skill) => ({\n\t\t\t\tname: `skill:${skill.name}`,\n\t\t\t\tdescription: skill.description,\n\t\t\t\tsource: \"skill\",\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t}));\n\n\t\t\treturn [...extensionCommands, ...templates, ...skills];\n\t\t};\n\n\t\trunner.bindCore(\n\t\t\t{\n\t\t\t\tsendMessage: (message, options) => {\n\t\t\t\t\tthis.sendCustomMessage(message, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tsendUserMessage: (content, options) => {\n\t\t\t\t\tthis.sendUserMessage(content, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_user_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tappendEntry: (customType, data) => {\n\t\t\t\t\tconst entryId = this.sessionManager.appendCustomEntry(customType, data);\n\t\t\t\t\tconst entry = this.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (entry) {\n\t\t\t\t\t\tthis._emit({ type: \"entry_appended\", entry });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tsetSessionName: (name) => {\n\t\t\t\t\tthis.setSessionName(name);\n\t\t\t\t},\n\t\t\t\tgetSessionName: () => {\n\t\t\t\t\treturn this.sessionManager.getSessionName();\n\t\t\t\t},\n\t\t\t\tsetLabel: (entryId, label) => {\n\t\t\t\t\tthis.sessionManager.appendLabelChange(entryId, label);\n\t\t\t\t},\n\t\t\t\tgetActiveTools: () => this.getActiveToolNames(),\n\t\t\t\tgetAllTools: () => this.getAllTools(),\n\t\t\t\tsetActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),\n\t\t\t\trefreshTools: () => this._refreshToolRegistry(),\n\t\t\t\tgetCommands,\n\t\t\t\tsetModel: async (model) => {\n\t\t\t\t\tif (!this._modelRuntime.hasConfiguredAuth(model.provider)) return false;\n\t\t\t\t\tawait this.setModel(model);\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tgetThinkingLevel: () => this.thinkingLevel,\n\t\t\t\tsetThinkingLevel: (level) => this.setThinkingLevel(level),\n\t\t\t},\n\t\t\t{\n\t\t\t\tgetModel: () => this.model,\n\t\t\t\tgetScopedModels: () => this._scopedModels,\n\t\t\t\tisIdle: () => this.isIdle,\n\t\t\t\tisProjectTrusted: () => this.settingsManager.isProjectTrusted(),\n\t\t\t\tgetSignal: () => this.agent.signal,\n\t\t\t\tabort: () => {\n\t\t\t\t\tif (this._extensionAbortHandler) {\n\t\t\t\t\t\tthis._extensionAbortHandler();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid this.abort();\n\t\t\t\t},\n\t\t\t\thasPendingMessages: () => this.pendingMessageCount > 0,\n\t\t\t\tshutdown: () => {\n\t\t\t\t\tthis._extensionShutdownHandler?.();\n\t\t\t\t},\n\t\t\t\tgetContextUsage: () => this.getContextUsage(),\n\t\t\t\tcompact: (options) => {\n\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst result = await this.compact(options?.customInstructions);\n\t\t\t\t\t\t\toptions?.onComplete?.(result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t\t\toptions?.onError?.(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t\tgetSystemPrompt: () => this.systemPrompt,\n\t\t\t\tgetSystemPromptOptions: () => this._baseSystemPromptOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tregisterProvider: (name, config) => {\n\t\t\t\t\tthis._modelRuntime.registerProvider(name, config);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tregisterNativeProvider: (provider) => {\n\t\t\t\t\tthis._modelRuntime.registerNativeProvider(provider);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tunregisterProvider: (name) => {\n\t\t\t\t\tthis._modelRuntime.unregisterProvider(name);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate _refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean }): void {\n\t\tconst previousRegistryNames = new Set(this._toolRegistry.keys());\n\t\tconst previousActiveToolNames = this.getActiveToolNames();\n\t\tconst allowedToolNames = this._allowedToolNames;\n\t\tconst excludedToolNames = this._excludedToolNames;\n\t\tconst isAllowedTool = (name: string): boolean =>\n\t\t\t(!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name);\n\n\t\tconst registeredTools = this._extensionRunner.getAllRegisteredTools();\n\t\tconst allCustomTools = [\n\t\t\t...registeredTools,\n\t\t\t...this._customTools.map((definition) => ({\n\t\t\t\tdefinition,\n\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: \"sdk\" }),\n\t\t\t})),\n\t\t].filter((tool) => isAllowedTool(tool.definition.name));\n\t\tconst definitionRegistry = new Map<string, ToolDefinitionEntry>(\n\t\t\tArray.from(this._baseToolDefinitions.entries())\n\t\t\t\t.filter(([name]) => isAllowedTool(name))\n\t\t\t\t.map(([name, definition]) => [\n\t\t\t\t\tname,\n\t\t\t\t\t{\n\t\t\t\t\t\tdefinition,\n\t\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: \"builtin\" }),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t);\n\t\tfor (const tool of allCustomTools) {\n\t\t\tdefinitionRegistry.set(tool.definition.name, {\n\t\t\t\tdefinition: tool.definition,\n\t\t\t\tsourceInfo: tool.sourceInfo,\n\t\t\t});\n\t\t}\n\t\tthis._toolDefinitions = definitionRegistry;\n\t\tthis._toolPromptSnippets = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst snippet = this._normalizePromptSnippet(definition.promptSnippet);\n\t\t\t\t\treturn snippet ? ([definition.name, snippet] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string] => entry !== undefined),\n\t\t);\n\t\tthis._toolPromptGuidelines = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);\n\t\t\t\t\treturn guidelines.length > 0 ? ([definition.name, guidelines] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string[]] => entry !== undefined),\n\t\t);\n\t\tconst runner = this._extensionRunner;\n\t\tconst wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);\n\t\tconst wrappedBuiltInTools = wrapRegisteredTools(\n\t\t\tArray.from(this._baseToolDefinitions.values())\n\t\t\t\t.filter((definition) => isAllowedTool(definition.name))\n\t\t\t\t.map((definition) => ({\n\t\t\t\t\tdefinition,\n\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: \"builtin\" }),\n\t\t\t\t})),\n\t\t\trunner,\n\t\t);\n\n\t\tconst toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));\n\t\tfor (const tool of wrappedExtensionTools as AgentTool[]) {\n\t\t\ttoolRegistry.set(tool.name, tool);\n\t\t}\n\t\tthis._toolRegistry = toolRegistry;\n\n\t\tconst nextActiveToolNames = (\n\t\t\toptions?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]\n\t\t).filter((name) => isAllowedTool(name));\n\n\t\tif (allowedToolNames) {\n\t\t\tfor (const toolName of this._toolRegistry.keys()) {\n\t\t\t\tif (allowedToolNames.has(toolName)) {\n\t\t\t\t\tnextActiveToolNames.push(toolName);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (options?.includeAllExtensionTools) {\n\t\t\tfor (const tool of wrappedExtensionTools) {\n\t\t\t\tnextActiveToolNames.push(tool.name);\n\t\t\t}\n\t\t} else if (!options?.activeToolNames) {\n\t\t\tfor (const toolName of this._toolRegistry.keys()) {\n\t\t\t\tif (!previousRegistryNames.has(toolName)) {\n\t\t\t\t\tnextActiveToolNames.push(toolName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.setActiveToolsByName([...new Set(nextActiveToolNames)]);\n\t}\n\n\tprivate _buildRuntime(options: {\n\t\tactiveToolNames?: string[];\n\t\tflagValues?: Map<string, boolean | string>;\n\t\tincludeAllExtensionTools?: boolean;\n\t}): void {\n\t\tconst autoResizeImages = this.settingsManager.getImageAutoResize();\n\t\tconst shellCommandPrefix = this.settingsManager.getShellCommandPrefix();\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst baseToolDefinitions = this._baseToolsOverride\n\t\t\t? Object.fromEntries(\n\t\t\t\t\tObject.entries(this._baseToolsOverride).map(([name, tool]) => [\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcreateToolDefinitionFromAgentTool(tool),\n\t\t\t\t\t]),\n\t\t\t\t)\n\t\t\t: createAllToolDefinitions(\n\t\t\t\t\tthis._cwd,\n\t\t\t\t\t{\n\t\t\t\t\t\tread: { autoResizeImages },\n\t\t\t\t\t\tbash: { commandPrefix: shellCommandPrefix, shellPath },\n\t\t\t\t\t},\n\t\t\t\t\tthis._resourceLoader.getAgentDir(),\n\t\t\t\t);\n\n\t\tthis._baseToolDefinitions = new Map(\n\t\t\tObject.entries(baseToolDefinitions).map(([name, tool]) => [name, tool as ToolDefinition]),\n\t\t);\n\n\t\t// The shared project memory tool is only exposed when enabled in settings.\n\t\tif (!this.settingsManager.getProjectMemoryEnabled()) {\n\t\t\tthis._baseToolDefinitions.delete(\"update_project_memory\");\n\t\t}\n\n\t\t// Ada Memory Engine tools (memory_add/replace/remove/search,\n\t\t// session_search, scratchpad, memory_status). Exposed when enabled.\n\t\t// Solo en la sesión principal: un subagente no debe poder mutar la\n\t\t// memoria durable del usuario (anti-recursión, igual que goal_* y\n\t\t// workflow abajo). Hasta ahora la propiedad se sostenía solo por la\n\t\t// allowlist de tools del call site, no por una guardia aquí.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine && !this._isSubagentSession && this.settingsManager.getProjectMemoryEnabled()) {\n\t\t\tconst engineTools = createMemoryEngineTools(memoryEngine);\n\t\t\tfor (const [name, tool] of Object.entries(engineTools)) {\n\t\t\t\tthis._baseToolDefinitions.set(name, tool as unknown as ToolDefinition);\n\t\t\t}\n\t\t}\n\n\t\t// Ada Intelligence Engine: tools del GoalEngine (resuelven el goal\n\t\t// activo por llamada). Registradas si el GoalEngine está habilitado.\n\t\t// Los subagentes NO reciben goal_*/workflow tools: solo el agente\n\t\t// principal orquesta el engine (anti-recursión y control de presupuesto).\n\t\tconst goalEngine = this._isSubagentSession ? null : this._resourceLoader.getGoalEngine?.();\n\t\tif (goalEngine && this.settingsManager.getGoalEngineEnabled()) {\n\t\t\tfor (const tool of goalSessionTools(goalEngine, () => this.sessionManager.getSessionId())) {\n\t\t\t\tthis._baseToolDefinitions.set(tool.name, tool as unknown as ToolDefinition);\n\t\t\t}\n\t\t\t// graph_add_nodes: registros puntuales (QA_FINDING/RISK/DECISION).\n\t\t\t// El spine se auto-registra vía GraphSync — esto es complemento.\n\t\t\tif (this.settingsManager.getGraphEnabled()) {\n\t\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\t\"graph_add_nodes\",\n\t\t\t\t\tcreateGraphTool(() => this._resourceLoader.getGraph?.()) as unknown as ToolDefinition,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Tool `workflow` SOLO en la sesión principal: los subagentes no\n\t\t// orquestan workflows (anti-anidamiento y control de presupuesto).\n\t\tif (!this._isSubagentSession) {\n\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\"workflow\",\n\t\t\t\tcreateWorkflowTool({\n\t\t\t\t\tgetManager: () => this._resourceLoader.getWorkflowManager?.(),\n\t\t\t\t\tconfirm: (prompt) => this._confirmWorkflowLaunch(prompt),\n\t\t\t\t\tagentDir: this._resourceLoader.getAgentDir(),\n\t\t\t\t\tsessionId: () => this.sessionManager.getSessionId(),\n\t\t\t\t}) as unknown as ToolDefinition,\n\t\t\t);\n\t\t}\n\n\t\tconst extensionsResult = this._resourceLoader.getExtensions();\n\t\tif (options.flagValues) {\n\t\t\tfor (const [name, value] of options.flagValues) {\n\t\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\t}\n\t\t}\n\n\t\tthis._extensionRunner = new ExtensionRunner(\n\t\t\textensionsResult.extensions,\n\t\t\textensionsResult.runtime,\n\t\t\tthis._cwd,\n\t\t\tthis.sessionManager,\n\t\t\tnew ModelRegistry(this._modelRuntime),\n\t\t);\n\t\tif (this._extensionRunnerRef) {\n\t\t\tthis._extensionRunnerRef.current = this._extensionRunner;\n\t\t}\n\t\tthis._bindExtensionCore(this._extensionRunner);\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\n\t\tconst defaultActiveToolNames = this._baseToolsOverride\n\t\t\t? Object.keys(this._baseToolsOverride)\n\t\t\t: [\"read\", \"bash\", \"edit\", \"write\"];\n\t\tconst baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;\n\t\tthis._refreshToolRegistry({\n\t\t\tactiveToolNames: baseActiveToolNames,\n\t\t\tincludeAllExtensionTools: options.includeAllExtensionTools,\n\t\t});\n\t}\n\n\tasync reload(options?: { beforeSessionStart?: () => void | Promise<void> }): Promise<void> {\n\t\tconst oldRunner = this._extensionRunner;\n\t\tconst previousFlagValues = oldRunner.getFlagValues();\n\t\tawait emitSessionShutdownEvent(oldRunner, { type: \"session_shutdown\", reason: \"reload\" });\n\t\toldRunner.invalidate();\n\t\tawait this.settingsManager.reload();\n\t\tthis.syncQueueModesFromSettings();\n\t\tresetApiProviders();\n\t\tawait this._resourceLoader.reload();\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this.getActiveToolNames(),\n\t\t\tflagValues: previousFlagValues,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\n\t\tconst hasBindings =\n\t\t\tthis._extensionUIContext ||\n\t\t\tthis._extensionCommandContextActions ||\n\t\t\tthis._extensionShutdownHandler ||\n\t\t\tthis._extensionErrorListener;\n\t\tif (hasBindings) {\n\t\t\tawait options?.beforeSessionStart?.();\n\t\t\tawait this._extensionRunner.emit({ type: \"session_start\", reason: \"reload\" });\n\t\t\tawait this.extendResourcesFromExtensions(\"reload\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Auto-Retry\n\t// =========================================================================\n\n\t/**\n\t * Check if an error is retryable (overloaded, rate limit, server errors).\n\t * Context overflow errors are NOT retryable (handled by compaction instead).\n\t */\n\tprivate _isRetryableError(message: AssistantMessage): boolean {\n\t\t// Context overflow is handled by compaction, not retry.\n\t\tif (isContextOverflow(message, this.model?.contextWindow ?? 0)) return false;\n\t\treturn isRetryableAssistantError(message);\n\t}\n\n\t/**\n\t * Retry policy + callbacks shared by compaction and branch-summary summarization calls.\n\t * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient\n\t * stream drop no longer fails the whole operation. `source` carries the context\n\t * the TUI needs to render the retry and recreate the underlying indicator.\n\t */\n\tprivate _summarizationRetryCallbacks(\n\t\tsource: { source: \"branchSummary\" } | { source: \"compaction\"; reason: \"manual\" | \"threshold\" | \"overflow\" },\n\t): RetryCallbacks {\n\t\treturn {\n\t\t\tonRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_scheduled\",\n\t\t\t\t\tattempt,\n\t\t\t\t\tmaxAttempts,\n\t\t\t\t\tdelayMs,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryAttemptStart: () => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_attempt_start\",\n\t\t\t\t\t...source,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryFinished: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_finished\" });\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Prepare a retryable error for continuation with exponential backoff.\n\t * @returns true if the caller should continue the agent, false otherwise\n\t */\n\tprivate async _prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._retryAttempt++;\n\n\t\tif (this._retryAttempt > settings.maxRetries) {\n\t\t\t// Preserve the completed attempt count so post-run handling can emit the final failure.\n\t\t\tthis._retryAttempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_start\",\n\t\t\tattempt: this._retryAttempt,\n\t\t\tmaxAttempts: settings.maxRetries,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove error message from agent state (keep in session for history)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\t// Wait with exponential backoff (abortable)\n\t\tthis._retryAbortController = new AbortController();\n\t\ttry {\n\t\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t\t} catch {\n\t\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\t\tconst attempt = this._retryAttempt;\n\t\t\tthis._retryAttempt = 0;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt,\n\t\t\t\tfinalError: \"Retry cancelled\",\n\t\t\t});\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._retryAbortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Cancel in-progress retry.\n\t */\n\tabortRetry(): void {\n\t\tthis._retryAbortController?.abort();\n\t}\n\n\t/** Whether auto-retry is currently in progress */\n\tget isRetrying(): boolean {\n\t\treturn this._retryAbortController !== undefined;\n\t}\n\n\t/** Whether auto-retry is enabled */\n\tget autoRetryEnabled(): boolean {\n\t\treturn this.settingsManager.getRetryEnabled();\n\t}\n\n\t/**\n\t * Toggle auto-retry setting.\n\t */\n\tsetAutoRetryEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setRetryEnabled(enabled);\n\t}\n\n\t// =========================================================================\n\t// Bash Execution\n\t// =========================================================================\n\n\t/**\n\t * Execute a bash command.\n\t * Adds result to agent context and session.\n\t * @param command The bash command to execute\n\t * @param onChunk Optional streaming callback for output\n\t * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n\t * @param options.id Optional identifier included in bash execution update events\n\t * @param options.operations Custom BashOperations for remote execution\n\t */\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: { excludeFromContext?: boolean; id?: string; operations?: BashOperations },\n\t): Promise<BashResult> {\n\t\tconst abortController = new AbortController();\n\t\tthis._bashAbortControllers.add(abortController);\n\n\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\n\t\ttry {\n\t\t\tconst result = await executeBashWithOperations(\n\t\t\t\tresolvedCommand,\n\t\t\t\tthis.sessionManager.getCwd(),\n\t\t\t\toptions?.operations ?? createLocalBashOperations({ shellPath }),\n\t\t\t\t{\n\t\t\t\t\tonChunk: (delta) => {\n\t\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\t\tthis._emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t\t},\n\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tthis.recordBashResult(command, result, options);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.isStreaming) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.sessionManager.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const abortController of [...this._bashAbortControllers]) {\n\t\t\tabortController.abort();\n\t\t}\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tprivate _flushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.sessionManager.appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n\n\t// =========================================================================\n\t// Session Management\n\t// =========================================================================\n\n\t/**\n\t * Set a display name for the current session.\n\t */\n\tsetSessionName(name: string): void {\n\t\tthis.sessionManager.appendSessionInfo(name);\n\t\tconst event = { type: \"session_info_changed\", name: this.sessionManager.getSessionName() } as const;\n\t\tthis._emit(event);\n\t\tvoid this._extensionRunner.emit(event);\n\t}\n\n\t// =========================================================================\n\t// Tree Navigation\n\t// =========================================================================\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tasync navigateTree(\n\t\ttargetId: string,\n\t\toptions: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string } = {},\n\t): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry }> {\n\t\tif (this.isStreaming) {\n\t\t\tthrow new Error(\"Wait for the current response to finish before navigating the session tree.\");\n\t\t}\n\n\t\tconst oldLeafId = this.sessionManager.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.model) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = this.sessionManager.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(\n\t\t\tthis.sessionManager,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tlet customInstructions = options.customInstructions;\n\t\tlet replaceInstructions = options.replaceInstructions;\n\t\tlet label = options.label;\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions,\n\t\t\treplaceInstructions,\n\t\t\tlabel,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tlet extensionSummary: { summary: string; details?: unknown; usage?: Usage } | undefined;\n\t\t\tlet fromExtension = false;\n\n\t\t\t// Emit session_before_tree event\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\t\tconst result = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_tree\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t})) as SessionBeforeTreeResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t}\n\n\t\t\t\tif (result?.summary && options.summarize) {\n\t\t\t\t\textensionSummary = result.summary;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\n\t\t\t\t// Allow extensions to override instructions and label\n\t\t\t\tif (result?.customInstructions !== undefined) {\n\t\t\t\t\tcustomInstructions = result.customInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.replaceInstructions !== undefined) {\n\t\t\t\t\treplaceInstructions = result.replaceInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.label !== undefined) {\n\t\t\t\t\tlabel = result.label;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Run default summarizer if needed\n\t\t\tlet summaryText: string | undefined;\n\t\t\tlet summaryDetails: unknown;\n\t\t\tlet summaryUsage: Usage | undefined;\n\t\t\tif (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {\n\t\t\t\tconst model = this.model!;\n\t\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);\n\t\t\t\tconst branchSummarySettings = this.settingsManager.getBranchSummarySettings();\n\t\t\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\t\t\tmodel: requestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tenv,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treplaceInstructions,\n\t\t\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\t\t\tstreamFn: this.agent.streamFunction,\n\t\t\t\t\tretry: this.settingsManager.getRetrySettings(),\n\t\t\t\t\tcallbacks: this._summarizationRetryCallbacks({ source: \"branchSummary\" }),\n\t\t\t\t});\n\t\t\t\tif (result.aborted) {\n\t\t\t\t\treturn { cancelled: true, aborted: true };\n\t\t\t\t}\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthrow new Error(result.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = result.summary;\n\t\t\t\tsummaryUsage = result.usage;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t\t};\n\t\t\t} else if (extensionSummary) {\n\t\t\t\tsummaryText = extensionSummary.summary;\n\t\t\t\tsummaryDetails = extensionSummary.details;\n\t\t\t\tsummaryUsage = extensionSummary.usage;\n\t\t\t}\n\n\t\t\t// Determine the new leaf position based on target type\n\t\t\tlet newLeafId: string | null;\n\t\t\tlet editorText: string | undefined;\n\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\t// User message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = contentText(targetEntry.message.content, \"\");\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\t// Custom message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = contentText(targetEntry.content, \"\");\n\t\t\t} else {\n\t\t\t\t// Non-user message: leaf = selected node\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\n\t\t\t// Switch leaf (with or without summary)\n\t\t\t// Summary is attached at the navigation target position (newLeafId), not the old branch\n\t\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\t\tif (summaryText) {\n\t\t\t\t// Create summary at target position (can be null for root)\n\t\t\t\tconst summaryId = this.sessionManager.branchWithSummary(\n\t\t\t\t\tnewLeafId,\n\t\t\t\t\tsummaryText,\n\t\t\t\t\tsummaryDetails,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\tsummaryUsage,\n\t\t\t\t);\n\t\t\t\tsummaryEntry = this.sessionManager.getEntry(summaryId) as BranchSummaryEntry;\n\n\t\t\t\t// Attach label to the summary entry\n\t\t\t\tif (label) {\n\t\t\t\t\tthis.sessionManager.appendLabelChange(summaryId, label);\n\t\t\t\t}\n\t\t\t} else if (newLeafId === null) {\n\t\t\t\t// No summary, navigating to root - reset leaf\n\t\t\t\tthis.sessionManager.resetLeaf();\n\t\t\t} else {\n\t\t\t\t// No summary, navigating to non-root\n\t\t\t\tthis.sessionManager.branch(newLeafId);\n\t\t\t}\n\n\t\t\t// Attach label to target entry when not summarizing (no summary entry to label)\n\t\t\tif (label && !summaryText) {\n\t\t\t\tthis.sessionManager.appendLabelChange(targetId, label);\n\t\t\t}\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\n\t\t\t// Emit session_tree event\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: this.sessionManager.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summaryText ? fromExtension : undefined,\n\t\t\t});\n\n\t\t\t// Emit to custom tools\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get all user messages from session for fork selector.\n\t */\n\tgetUserMessagesForForking(): Array<{ entryId: string; text: string }> {\n\t\tconst entries = this.sessionManager.getEntries();\n\t\tconst result: Array<{ entryId: string; text: string }> = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tif (entry.message.role !== \"user\") continue;\n\n\t\t\tconst text = contentText(entry.message.content, \"\");\n\t\t\tif (text) {\n\t\t\t\tresult.push({ entryId: entry.id, text });\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get session statistics. Aggregates over ALL session entries (including\n\t * history that was compacted away), so token/cost totals reflect what was\n\t * actually billed across the session.\n\t */\n\tgetSessionStats(): SessionStats {\n\t\tlet userMessages = 0;\n\t\tlet assistantMessages = 0;\n\t\tlet toolResults = 0;\n\t\tlet totalMessages = 0;\n\t\tlet toolCalls = 0;\n\t\tconst usageTotals = createUsageTotals();\n\n\t\tfor (const entry of this.sessionManager.getEntries()) {\n\t\t\tif ((entry.type === \"branch_summary\" || entry.type === \"compaction\") && entry.usage) {\n\t\t\t\taddUsageToTotals(usageTotals, entry.usage);\n\t\t\t}\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\ttotalMessages++;\n\t\t\tconst message = entry.message;\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tuserMessages++;\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\ttoolResults++;\n\t\t\t\tif (message.usage) {\n\t\t\t\t\taddUsageToTotals(usageTotals, message.usage);\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tassistantMessages++;\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tif (Array.isArray(assistantMsg.content)) {\n\t\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\t}\n\t\t\t\taddUsageToTotals(usageTotals, assistantMsg.usage);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tsessionFile: this.sessionFile,\n\t\t\tsessionId: this.sessionId,\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages,\n\t\t\ttokens: {\n\t\t\t\tinput: usageTotals.input,\n\t\t\t\toutput: usageTotals.output,\n\t\t\t\tcacheRead: usageTotals.cacheRead,\n\t\t\t\tcacheWrite: usageTotals.cacheWrite,\n\t\t\t\ttotal: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,\n\t\t\t},\n\t\t\tcost: usageTotals.cost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t};\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// We can only trust usage from an assistant that responded after the latest compaction.\n\t\t// If no such assistant exists, context token count is unknown until the next LLM response.\n\t\tconst branchEntries = this.sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\n\t\tif (latestCompaction) {\n\t\t\t// Check if there's a valid assistant usage after the compaction boundary\n\t\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\t\tlet hasPostCompactionUsage = false;\n\t\t\tfor (let i = branchEntries.length - 1; i > compactionIndex; i--) {\n\t\t\t\tconst entry = branchEntries[i];\n\t\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\") {\n\t\t\t\t\t\tconst contextTokens = calculateContextTokens(assistant.usage);\n\t\t\t\t\t\tif (contextTokens > 0) {\n\t\t\t\t\t\t\thasPostCompactionUsage = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasPostCompactionUsage) {\n\t\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t\t}\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.messages);\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @param options Optional export presentation settings\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string, options: { themeName?: string } = {}): Promise<string> {\n\t\tconst themeName = [options.themeName, this.settingsManager.getTheme()].find(\n\t\t\t(candidate) => candidate !== undefined && getThemeByName(candidate) !== undefined,\n\t\t);\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(this.sessionManager, this.state, {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = this.sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t// =========================================================================\n\t// Utilities\n\t// =========================================================================\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.messages\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\tcreateReplacedSessionContext(): ReplacedSessionContext {\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()),\n\t\t) as ReplacedSessionContext;\n\t\tcontext.sendMessage = (message, options) => this.sendCustomMessage(message, options);\n\t\tcontext.sendUserMessage = (content, options) => this.sendUserMessage(content, options);\n\t\treturn context;\n\t}\n\n\t/**\n\t * Check if extensions have handlers for a specific event type.\n\t */\n\thasExtensionHandlers(eventType: string): boolean {\n\t\treturn this._extensionRunner.hasHandlers(eventType);\n\t}\n\n\t/**\n\t * Get the extension runner (for setting UI context and error handlers).\n\t */\n\tget extensionRunner(): ExtensionRunner {\n\t\treturn this._extensionRunner;\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AgentSession - Core abstraction for agent lifecycle and session management.\n *\n * This class is shared between all run modes (interactive, print, rpc).\n * It encapsulates:\n * - Agent state access\n * - Event subscription with automatic session persistence\n * - Model and thinking level management\n * - Compaction (manual and auto)\n * - Bash execution\n * - Session switching and branching\n *\n * Modes use this class and add their own I/O layer on top.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname } from \"node:path\";\nimport type {\n\tAgent,\n\tAgentEvent,\n\tAgentMessage,\n\tAgentState,\n\tAgentTool,\n\tPrepareNextTurnContext,\n\tThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport { contentText } from \"@earendil-works/pi-ai\";\nimport type {\n\tAssistantMessage,\n\tAuthResult,\n\tImageContent,\n\tModel,\n\tProviderHeaders,\n\tTextContent,\n\tUsage,\n} from \"@earendil-works/pi-ai/compat\";\nimport {\n\tclampThinkingLevel,\n\tcleanupSessionResources,\n\tgetSupportedThinkingLevels,\n\tisContextOverflow,\n\tisRecoverableLength,\n\tisRetryableAssistantError,\n\tmodelsAreEqual,\n\ttype RetryCallbacks,\n\tresetApiProviders,\n\tstreamSimple,\n} from \"@earendil-works/pi-ai/compat\";\nimport { getThemeByName, theme } from \"../modes/interactive/theme/theme.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { normalizeToolResultImages } from \"../utils/tool-result-images.ts\";\nimport {\n\ttype AdvisorDeSesion,\n\tabrirTurno,\n\tajustarPresupuestoDeRazonamiento,\n\tanadirRegistroDenso,\n\tbuildEnginePersonalityBlock,\n\tbuscarConElModelo,\n\tcerrarLlamadasAbiertas,\n\tconstruirAdvisorDeSesion,\n\tconstruirDesglose,\n\tcrearBrowserTools,\n\tcrearComputerTools,\n\tcrearEstructuralTools,\n\tcrearInteractivoTools,\n\tcrearObtenerClave,\n\tcreateGraphTool,\n\tcreateMcpTools,\n\tcreateMemoryEngineTools,\n\tcreateWebTools,\n\tcreateWorkflowTool,\n\tdispararHooks,\n\tenvolverReadConPdf,\n\tenvolverReadConUrlsInternas,\n\testadoDelTurno,\n\texpandVaultRefs,\n\tGraphSync,\n\tgoalSessionTools,\n\tleerDenegadas,\n\tloadProjectMemorySync,\n\tmensajeDeTurnoVacio,\n\tnivelDesdeConfig,\n\toptimizeUserPrompt,\n\tRegistroDeSesion,\n\tredactSensitive,\n\tsaveProjectMemory,\n\tsoportaBusquedaNativa,\n\ttranscriptParaAdvisor,\n} from \"./ada.ts\";\nimport { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from \"./auth-guidance.ts\";\nimport { type BashResult, executeBashWithOperations } from \"./bash-executor.ts\";\nimport {\n\ttype CompactionPreparation,\n\ttype CompactionResult,\n\tcalculateContextTokens,\n\tcollectEntriesForBranchSummary,\n\tcompact,\n\testimateContextTokens,\n\testimateTokens,\n\tgenerateBranchSummary,\n\tprepareCompaction,\n\tshouldCompact,\n} from \"./compaction/index.ts\";\nimport { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from \"./defaults.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport {\n\ttype ContextUsage,\n\ttype ExtensionCommandContextActions,\n\ttype ExtensionErrorListener,\n\ttype ExtensionMode,\n\tExtensionRunner,\n\ttype ExtensionUIContext,\n\ttype InputSource,\n\ttype MessageEndEvent,\n\ttype MessageStartEvent,\n\ttype MessageUpdateEvent,\n\ttype ReplacedSessionContext,\n\ttype SessionBeforeCompactResult,\n\ttype SessionBeforeTreeResult,\n\ttype SessionCompactFailedEvent,\n\ttype SessionStartEvent,\n\ttype ShutdownHandler,\n\ttype ToolDefinition,\n\ttype ToolExecutionEndEvent,\n\ttype ToolExecutionStartEvent,\n\ttype ToolExecutionUpdateEvent,\n\ttype ToolInfo,\n\ttype TreePreparation,\n\ttype TurnEndEvent,\n\ttype TurnStartEvent,\n\twrapRegisteredTools,\n} from \"./extensions/index.ts\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.ts\";\nimport type { BashExecutionMessage, CustomMessage } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport type { ModelRuntime } from \"./model-runtime.ts\";\nimport { expandPromptTemplate, type PromptTemplate } from \"./prompt-templates.ts\";\nimport type { ResourceExtensionPaths, ResourceLoader } from \"./resource-loader.ts\";\nimport type { BranchSummaryEntry, CompactionEntry, SessionEntry, SessionManager } from \"./session-manager.ts\";\nimport { CURRENT_SESSION_VERSION, getLatestCompactionEntry, type SessionHeader } from \"./session-manager.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { SlashCommandInfo } from \"./slash-commands.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport { type BuildSystemPromptOptions, buildSystemPrompt } from \"./system-prompt.ts\";\nimport { type BashOperations, createLocalBashOperations } from \"./tools/bash.ts\";\nimport { createAllToolDefinitions } from \"./tools/index.ts\";\nimport { createToolDefinitionFromAgentTool } from \"./tools/tool-definition-wrapper.ts\";\nimport { addUsageToTotals, createUsageTotals } from \"./usage-totals.ts\";\nimport { publicarAnotarRed } from \"./web/registro-red.ts\";\n\n// ============================================================================\n// Skill Block Parsing\n// ============================================================================\n\n/** Parsed skill block from a user message */\nexport interface ParsedSkillBlock {\n\tname: string;\n\tlocation: string;\n\tcontent: string;\n\tuserMessage: string | undefined;\n}\n\n/**\n * Parse a skill block from message text.\n * Returns null if the text doesn't contain a skill block.\n */\nexport function parseSkillBlock(text: string): ParsedSkillBlock | null {\n\tconst match = text.match(/^<skill name=\"([^\"]+)\" location=\"([^\"]+)\">\\n([\\s\\S]*?)\\n<\\/skill>(?:\\n\\n([\\s\\S]+))?$/);\n\tif (!match) return null;\n\treturn {\n\t\tname: match[1],\n\t\tlocation: match[2],\n\t\tcontent: match[3],\n\t\tuserMessage: match[4]?.trim() || undefined,\n\t};\n}\n\n/** Session-specific events that extend the core AgentEvent */\nexport type AgentSessionEvent =\n\t| Exclude<AgentEvent, { type: \"agent_end\" }>\n\t| {\n\t\t\ttype: \"agent_end\";\n\t\t\tmessages: AgentMessage[];\n\t\t\twillRetry: boolean;\n\t }\n\t| { type: \"agent_settled\" }\n\t| {\n\t\t\ttype: \"notification\";\n\t\t\tmessage: string;\n\t\t\tlevel?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"entry_appended\"; entry: SessionEntry }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: CompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| {\n\t\t\ttype: \"summarization_retry_scheduled\";\n\t\t\tattempt: number;\n\t\t\tmaxAttempts: number;\n\t\t\tdelayMs: number;\n\t\t\terrorMessage: string;\n\t }\n\t| { type: \"summarization_retry_attempt_start\"; source: \"branchSummary\" }\n\t| {\n\t\t\ttype: \"summarization_retry_attempt_start\";\n\t\t\tsource: \"compaction\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t }\n\t| { type: \"summarization_retry_finished\" }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string }\n\t| { type: \"bash_execution_update\"; id?: string; delta: string }\n\t| {\n\t\t\ttype: \"prompt_optimization\";\n\t\t\tdecision: \"clarify\" | \"enhance\";\n\t\t\toriginal: string;\n\t\t\tenhanced?: string;\n\t\t\tquestions?: string[];\n\t\t\tsummary?: string;\n\t };\n\n/** Listener function for agent session events */\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nfunction withoutDeletedHeaders(headers: ProviderHeaders | undefined): Record<string, string> | undefined {\n\treturn headers\n\t\t? Object.fromEntries(Object.entries(headers).filter((entry): entry is [string, string] => entry[1] !== null))\n\t\t: undefined;\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\t/** Models to cycle through with Ctrl+P (from --models flag) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\t/** Resource loader for extensions, skills, prompts, themes, context files, and system prompt */\n\tresourceLoader: ResourceLoader;\n\t/** SDK custom tools registered outside extensions */\n\tcustomTools?: ToolDefinition[];\n\t/** Subagentes: desactiva la continuación automática del GoalEngine (anti-recursión). */\n\tisSubagent?: boolean;\n\t/** Canonical model/auth runtime used by coding-agent internals. */\n\tmodelRuntime: ModelRuntime;\n\t/** Initial active built-in tool names. Default: [read, bash, edit, write] */\n\tinitialActiveToolNames?: string[];\n\t/** Optional allowlist of tool names. When provided, only these tool names are exposed. */\n\tallowedToolNames?: string[];\n\t/** Optional denylist of tool names. When provided, these tool names are not exposed. */\n\texcludedToolNames?: string[];\n\t/**\n\t * Override base tools (useful for custom runtimes).\n\t *\n\t * These are synthesized into minimal ToolDefinitions internally so AgentSession can keep\n\t * a definition-first registry even when callers provide plain AgentTool instances.\n\t */\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\t/** Mutable ref used by Agent to access the current ExtensionRunner */\n\textensionRunnerRef?: { current?: ExtensionRunner };\n\t/** Session start event metadata emitted when extensions bind to this runtime. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tmode?: ExtensionMode;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tabortHandler?: () => void;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\n/** Options for AgentSession.prompt() */\nexport interface PromptOptions {\n\t/** Whether to dispatch extension commands and expand skill commands and prompt templates (default: true) */\n\texpandPromptTemplates?: boolean;\n\t/** Image attachments */\n\timages?: ImageContent[];\n\t/** When streaming, how to queue the message: \"steer\" (interrupt) or \"followUp\" (wait). Required if streaming. */\n\tstreamingBehavior?: \"steer\" | \"followUp\";\n\t/** Source of input for extension input event handlers. Defaults to \"interactive\". */\n\tsource?: InputSource;\n\t/** Internal hook used by RPC mode to observe prompt preflight acceptance or rejection. */\n\tpreflightResult?: (success: boolean) => void;\n}\n\n/** Options for model/thinking mutations. */\nexport interface ModelMutationOptions {\n\t/** Persist the new value to global defaults. Defaults to session-only. */\n\tpersist?: boolean;\n}\n\n/** Result from cycleModel() */\nexport interface ModelCycleResult {\n\tmodel: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\t/** Whether cycling through scoped models (--models flag) or all available */\n\tisScoped: boolean;\n}\n\n/** Session statistics for /session command */\nexport interface SessionStats {\n\t/**\n\t * De qué está hecha la ventana de contexto ahora mismo.\n\t *\n\t * Viaja aquí, dentro de las estadísticas que el desktop ya pide, en vez de\n\t * como tipo de mensaje RPC nuevo: añadirlo al protocolo costaba hunks en\n\t * `rpc-types.ts` y `rpc-mode.ts`, que son de upstream y hoy están casi\n\t * intactos. Undefined cuando el modelo no declara ventana de contexto.\n\t */\n\tcontextBreakdown?: import(\"./context-breakdown.ts\").DesgloseContexto;\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\ninterface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\nfunction estimateMessagesTokens(messages: AgentMessage[]): number {\n\tlet tokens = 0;\n\tfor (const message of messages) {\n\t\ttokens += estimateTokens(message);\n\t}\n\treturn tokens;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n// ============================================================================\n// AgentSession Class\n// ============================================================================\n\nexport class AgentSession {\n\treadonly agent: Agent;\n\treadonly sessionManager: SessionManager;\n\treadonly settingsManager: SettingsManager;\n\n\tprivate _scopedModels: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t// Event subscription state\n\tprivate _unsubscribeAgent?: () => void;\n\tprivate _eventListeners: AgentSessionEventListener[] = [];\n\tprivate _isAgentRunActive = false;\n\tprivate _idleWaitPromise: Promise<void> | undefined;\n\tprivate _resolveIdleWait: (() => void) | undefined;\n\n\t/** Tracks pending steering messages for UI display. Removed when delivered. */\n\tprivate _steeringMessages: string[] = [];\n\t/** Tracks pending follow-up messages for UI display. Removed when delivered. */\n\tprivate _followUpMessages: string[] = [];\n\t/** Messages queued to be included with the next user prompt as context (\"asides\"). */\n\tprivate _pendingNextTurnMessages: CustomMessage[] = [];\n\n\t// Compaction state\n\tprivate _compactionAbortController: AbortController | undefined = undefined;\n\tprivate _autoCompactionAbortController: AbortController | undefined = undefined;\n\tprivate _overflowRecoveryAttempted = false;\n\n\t// Branch summarization state\n\tprivate _branchSummaryAbortController: AbortController | undefined = undefined;\n\n\t// Retry state\n\tprivate _retryAbortController: AbortController | undefined = undefined;\n\tprivate _retryAttempt = 0;\n\n\t// Bash execution state\n\tprivate readonly _bashAbortControllers = new Set<AbortController>();\n\tprivate _pendingBashMessages: BashExecutionMessage[] = [];\n\n\t// Extension system\n\tprivate _extensionRunner!: ExtensionRunner;\n\tprivate _turnIndex = 0;\n\n\tprivate _resourceLoader: ResourceLoader;\n\tprivate _customTools: ToolDefinition[];\n\tprivate _baseToolDefinitions: Map<string, ToolDefinition> = new Map();\n\tprivate _cwd: string;\n\tprivate _extensionRunnerRef?: { current?: ExtensionRunner };\n\tprivate _initialActiveToolNames?: string[];\n\tprivate _allowedToolNames?: Set<string>;\n\tprivate _excludedToolNames?: Set<string>;\n\tprivate _baseToolsOverride?: Record<string, AgentTool>;\n\tprivate _sessionStartEvent: SessionStartEvent;\n\tprivate _extensionUIContext?: ExtensionUIContext;\n\tprivate _extensionMode: ExtensionMode = \"print\";\n\tprivate _extensionCommandContextActions?: ExtensionCommandContextActions;\n\tprivate _extensionAbortHandler?: () => void;\n\tprivate _extensionShutdownHandler?: ShutdownHandler;\n\tprivate _extensionErrorListener?: ExtensionErrorListener;\n\tprivate _extensionErrorUnsubscriber?: () => void;\n\n\tprivate _modelRuntime: ModelRuntime;\n\n\t// ── Campos de Ada ────────────────────────────────────────────────────────\n\t// Juntos y no repartidos por la clase a propósito: cada bloque separado en\n\t// este fichero de upstream es un hunk más que reaplicar en cada sync.\n\tprivate _isSubagentSession = false;\n\tprivate _graphSync?: GraphSync;\n\tprivate _advisor?: AdvisorDeSesion;\n\n\t/** Una vuelta del advisor sobre la sesión. Nunca espera ni lanza. */\n\tprivate _mirarConAdvisor(): void {\n\t\tthis._advisor ??= construirAdvisorDeSesion({\n\t\t\tsettings: this.settingsManager,\n\t\t\tmodelo: () => this.model,\n\t\t\truntime: this._modelRuntime,\n\t\t\tentregar: (texto, severidad) => {\n\t\t\t\tvoid this.sendCustomMessage(\n\t\t\t\t\t{\n\t\t\t\t\t\tcustomType: \"advisor\",\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: texto }],\n\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\tdetails: { severidad },\n\t\t\t\t\t},\n\t\t\t\t\t{ deliverAs: \"steer\" },\n\t\t\t\t);\n\t\t\t},\n\t\t\tlibro: this._registro.libro(),\n\t\t});\n\t\tthis._advisor.trasTurno(transcriptParaAdvisor(this.agent.state.messages as never));\n\t}\n\tprivate readonly _registro = new RegistroDeSesion(\n\t\t() => this._resourceLoader.getAgentDir(),\n\t\t() => this.sessionId,\n\t\t() => this._cwd,\n\t\tasync (texto) => {\n\t\t\t// Sin modelo elegido, ni lo intentamos: que `intentarReparar` lo lea\n\t\t\t// como \"no-aplicable\", no como una llamada al modelo que devolvió vacío.\n\t\t\tconst modelo = this.model;\n\t\t\tif (!modelo) return null;\n\t\t\ttry {\n\t\t\t\tconst respuesta = await this._modelRuntime.completeSimple(modelo, {\n\t\t\t\t\tmessages: [{ role: \"user\", content: texto }],\n\t\t\t\t} as never);\n\t\t\t\treturn contentText(respuesta.content, \"\").trim();\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t);\n\n\t/** Optimizador autónomo de prompts: \"off\" | \"auto\" | \"clarify\". */\n\tprivate _promptOptimizerMode: \"off\" | \"auto\" | \"clarify\" = \"off\";\n\t/** Clarificación pendiente (modo \"clarify\"): original + preguntas ya emitidas. */\n\tprivate _pendingClarification: { original: string; questions: string[]; answers?: string[] } | undefined;\n\n\t// Tool registry for extension getTools/setTools\n\tprivate _toolRegistry: Map<string, AgentTool> = new Map();\n\tprivate _toolDefinitions: Map<string, ToolDefinitionEntry> = new Map();\n\tprivate _toolPromptSnippets: Map<string, string> = new Map();\n\tprivate _toolPromptGuidelines: Map<string, string[]> = new Map();\n\n\t// Base system prompt (without extension appends) - used to apply fresh appends each turn\n\tprivate _baseSystemPrompt = \"\";\n\tprivate _baseSystemPromptOptions!: BuildSystemPromptOptions;\n\tprivate _systemPromptOverride?: string;\n\n\tconstructor(config: AgentSessionConfig) {\n\t\tthis.agent = config.agent;\n\t\tthis.sessionManager = config.sessionManager;\n\t\tthis.settingsManager = config.settingsManager;\n\t\tthis._scopedModels = config.scopedModels ?? [];\n\t\tthis._resourceLoader = config.resourceLoader;\n\t\tthis._customTools = config.customTools ?? [];\n\t\t// ANTES de `_buildRuntime()`, que consulta este campo para decidir qué\n\t\t// tools recibe un subagente. Moverlo más abajo para ahorrar un hunk hizo\n\t\t// que se leyera `false` y los subagentes recibieran memory_add/search —\n\t\t// justo lo que la anti-recursión existe para impedir.\n\t\tthis._isSubagentSession = config.isSubagent === true;\n\t\tthis._cwd = config.cwd;\n\t\tthis._modelRuntime = config.modelRuntime;\n\t\tthis._extensionRunnerRef = config.extensionRunnerRef;\n\t\tthis._initialActiveToolNames = config.initialActiveToolNames;\n\t\tthis._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;\n\t\tthis._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;\n\t\tthis._baseToolsOverride = config.baseToolsOverride;\n\t\tthis._sessionStartEvent = config.sessionStartEvent ?? { type: \"session_start\", reason: \"startup\" };\n\n\t\t// Always subscribe to agent events for internal handling\n\t\t// (session persistence, extensions, auto-compaction, retry logic)\n\t\tthis._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);\n\t\tthis._installAgentToolHooks();\n\t\tthis._installAgentNextTurnRefresh();\n\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this._initialActiveToolNames,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\n\t\t// Los subagentes (goals/workflows) HEREDAN el modelo de la sesión principal:\n\t\t// misma provider/modelId → misma auth → misma API key del chat.\n\t\tthis._resourceLoader.setSessionModelGetter?.(() => {\n\t\t\tconst m = this.model;\n\t\t\treturn m\n\t\t\t\t? {\n\t\t\t\t\t\tprovider: m.provider,\n\t\t\t\t\t\tmodelId: (m as { modelId?: string; id?: string }).modelId ?? (m as { id?: string }).id ?? \"\",\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\t});\n\n\t\t// Cuando un run de workflow arranca (tool del agente o sub-tarea del\n\t\t// goal), la sesión emite workflow_started — el desktop lo usa para\n\t\t// abrir el panel Engine automáticamente.\n\t\tthis._resourceLoader.setOnWorkflowRunStarted?.((runId, name) => {\n\t\t\tthis._emit({ type: \"workflow_started\", runId, name } as never);\n\t\t});\n\n\t\t// Ada Memory Engine: lazy-initialize (stores, migration, session index)\n\t\t// and wire the LLM callback used by review/correction/flush. Best-effort\n\t\t// and never blocks startup.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine) {\n\t\t\tmemoryEngine.setSessionId(this.sessionManager.getSessionId());\n\t\t\tmemoryEngine.setComplete(async (systemPrompt: string, userContent: string) => {\n\t\t\t\tconst model = this.model;\n\t\t\t\tif (!model) throw new Error(\"No model selected\");\n\t\t\t\tconst result = await this._modelRuntime.completeSimple(model, {\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t\tmessages: [{ role: \"user\", content: userContent }],\n\t\t\t\t} as never);\n\t\t\t\treturn contentText(result.content, \"\").trim();\n\t\t\t});\n\t\t\tmemoryEngine.setNotify((message, level) => {\n\t\t\t\tthis._emit({ type: \"notification\", message, level });\n\t\t\t});\n\t\t\tmemoryEngine.setSensitiveDetected((reason) => {\n\t\t\t\t// Persistent, highlighted message inside the conversation (CLI +\n\t\t\t\t// desktop) — never a transient banner.\n\t\t\t\tvoid this.sendCustomMessage({\n\t\t\t\t\tcustomType: \"sensitive-notice\",\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Has introducido un dato sensible que no se almacenará en memoria. ${reason}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\tdetails: { engine: \"ada-memory\", kind: \"sensitive\" },\n\t\t\t\t});\n\t\t\t});\n\t\t\tvoid memoryEngine.initialize().catch(() => {\n\t\t\t\t// Memory must never break the session.\n\t\t\t});\n\t\t}\n\t}\n\n\tget modelRuntime(): ModelRuntime {\n\t\treturn this._modelRuntime;\n\t}\n\n\tprivate async _getRequiredRequestAuth(model: Model<any>): Promise<{\n\t\tmodel: Model<any>;\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t\tenv?: Record<string, string>;\n\t}> {\n\t\tlet result: AuthResult | undefined;\n\t\ttry {\n\t\t\tresult = await this._modelRuntime.getAuth(model);\n\t\t} catch (error) {\n\t\t\tconst cause = error instanceof Error ? error.cause : undefined;\n\t\t\tif (cause instanceof Error && cause.message === \"authHeader requires a resolved API key\") {\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\tif (result && (result.auth.apiKey || result.auth.headers)) {\n\t\t\tconst requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;\n\t\t\treturn {\n\t\t\t\tmodel: requestModel,\n\t\t\t\tapiKey: result.auth.apiKey,\n\t\t\t\theaders: withoutDeletedHeaders(result.auth.headers),\n\t\t\t\tenv: result.env,\n\t\t\t};\n\t\t}\n\n\t\tconst isOAuth = this._modelRuntime.isUsingOAuth(model.provider);\n\t\tif (isOAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t`Authentication failed for \"${model.provider}\". ` +\n\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t`Run '/login ${model.provider}' to re-authenticate.`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(formatNoApiKeyFoundMessage(model.provider));\n\t}\n\n\tprivate async _getSummarizationRequestAuth(model: Model<any>): Promise<{\n\t\tmodel: Model<any>;\n\t\tapiKey?: string;\n\t\theaders?: Record<string, string>;\n\t\tenv?: Record<string, string>;\n\t}> {\n\t\tif (this.agent.streamFunction === streamSimple) {\n\t\t\treturn this._getRequiredRequestAuth(model);\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this._modelRuntime.getAuth(model);\n\t\t\tif (!result) return { model };\n\t\t\tconst requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;\n\t\t\treturn {\n\t\t\t\tmodel: requestModel,\n\t\t\t\tapiKey: result.auth.apiKey,\n\t\t\t\theaders: withoutDeletedHeaders(result.auth.headers),\n\t\t\t\tenv: result.env,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn { model };\n\t\t}\n\t}\n\n\t/**\n\t * Install tool hooks once on the Agent instance.\n\t *\n\t * The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the\n\t * new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt\n\t * registered tool execution to the extension context. Tool call and tool result interception now\n\t * happens here instead of in wrappers.\n\t */\n\tprivate _installAgentToolHooks(): void {\n\t\tthis.agent.beforeToolCall = async ({ toolCall, args }) => {\n\t\t\t// Política de aprobación por herramienta.\n\t\t\t//\n\t\t\t// Va ANTES que los hooks del usuario a propósito: las rutas\n\t\t\t// protegidas y el `deny` explícito no son negociables, y dejar que un\n\t\t\t// hook se ejecute primero le daría ocasión de ver los argumentos de\n\t\t\t// una llamada que nunca debió llegar a considerarse.\n\t\t\t//\n\t\t\t// Se bloquea con `{block, reason}` y no lanzando: el modelo recibe un\n\t\t\t// resultado de error legible que le dice qué hacer en su lugar, en vez\n\t\t\t// de una excepción.\n\t\t\tconst decisionAprobacion = await this._registro.antesDeLlamada(\n\t\t\t\ttoolCall.id,\n\t\t\t\ttoolCall.name,\n\t\t\t\targs as Record<string, unknown>,\n\t\t\t\tthis.settingsManager.getEngineConfig(),\n\t\t\t);\n\t\t\tif (decisionAprobacion.veredicto === \"denegada\") {\n\t\t\t\treturn { block: true, reason: decisionAprobacion.motivo };\n\t\t\t}\n\n\t\t\t// Hooks del usuario (`settings.hooks.events.pre_tool_use`). Sale sin\n\t\t\t// tocar nada si no hay ninguno configurado — ver core/hooks/.\n\t\t\tconst vetoPrevio = await dispararHooks(this.settingsManager.getEngineConfig(), \"pre_tool_use\", toolCall.name, {\n\t\t\t\tsessionId: this.sessionId,\n\t\t\t\tcwd: this._cwd,\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\ttoolInput: args,\n\t\t\t});\n\t\t\tif (vetoPrevio.bloquea) throw new Error(vetoPrevio.motivo);\n\n\t\t\tconst runner = this._extensionRunner;\n\t\t\tif (!runner.hasHandlers(\"tool_call\")) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\treturn await runner.emitToolCall({\n\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(err)}`);\n\t\t\t}\n\t\t};\n\n\t\tthis.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {\n\t\t\t// `post_tool_use` es informativo: la herramienta YA corrió, así que su\n\t\t\t// veredicto no puede vetar nada y solo se dispara para que el hook se\n\t\t\t// entere. Bloquear aquí daría falsa sensación de control.\n\t\t\tvoid dispararHooks(this.settingsManager.getEngineConfig(), \"post_tool_use\", toolCall.name, {\n\t\t\t\tsessionId: this.sessionId,\n\t\t\t\tcwd: this._cwd,\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\ttoolInput: args,\n\t\t\t\ttoolResult: result.content,\n\t\t\t}).catch(() => {});\n\t\t\t// Auto-registro del grafo (best-effort, nunca bloquea el agente).\n\t\t\ttry {\n\t\t\t\tif (!this._graphSync) {\n\t\t\t\t\tthis._graphSync = new GraphSync({\n\t\t\t\t\t\tgraph: () => this._resourceLoader.getGraph?.(),\n\t\t\t\t\t\tcwd: this._cwd,\n\t\t\t\t\t\tactiveGoal: () => null, // resuelto en agent_settled vía syncGoal\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tvoid this._graphSync.onToolResult(toolCall.name, args as Record<string, unknown>, result, isError);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t\t// Libro de sesión: qué HIZO el agente, encadenado y verificable.\n\t\t\t//\n\t\t\t// Este es el punto por el que pasan TODAS las tools, que es lo que\n\t\t\t// hace que el registro sea completo por construcción en vez de por\n\t\t\t// haberse acordado de instrumentar cada una.\n\t\t\t//\n\t\t\t// De momento anota la ejecución y, para `bash`, el comando. Los\n\t\t\t// hashes de fichero antes/después llegan con el observador de\n\t\t\t// ediciones, que ya tiene que leer las dos versiones: hacerlo aquí\n\t\t\t// obligaría a leer el fichero por segundo vez.\n\t\t\tconst avisoEdicion = await this._registro.trasLlamada(\n\t\t\t\ttoolCall.id,\n\t\t\t\ttoolCall.name,\n\t\t\t\targs as Record<string, unknown>,\n\t\t\t\tisError,\n\t\t\t);\n\t\t\tif (avisoEdicion !== null && Array.isArray(result.content)) {\n\t\t\t\tresult.content.push({ type: \"text\", text: avisoEdicion });\n\t\t\t}\n\n\t\t\tconst runner = this._extensionRunner;\n\t\t\tconst hookResult = runner.hasHandlers(\"tool_result\")\n\t\t\t\t? await runner.emitToolResult({\n\t\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t\t\tcontent: result.content,\n\t\t\t\t\t\tdetails: result.details,\n\t\t\t\t\t\tisError,\n\t\t\t\t\t\tusage: result.usage,\n\t\t\t\t\t})\n\t\t\t\t: undefined;\n\n\t\t\tconst content = hookResult?.content ?? result.content ?? [];\n\t\t\t// Runs after the extension hook so images injected or replaced by extensions are normalized too.\n\t\t\tconst normalizedContent = await normalizeToolResultImages(content, {\n\t\t\t\tautoResizeImages: this.settingsManager.getImageAutoResize(),\n\t\t\t});\n\n\t\t\tif (!hookResult && normalizedContent === content) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: normalizedContent,\n\t\t\t\tdetails: hookResult?.details,\n\t\t\t\tisError: hookResult?.isError ?? isError,\n\t\t\t\tusage: hookResult?.usage,\n\t\t\t};\n\t\t};\n\t}\n\n\tprivate _installAgentNextTurnRefresh(): void {\n\t\tconst previousPrepareNextTurnWithContext =\n\t\t\tthis.agent.prepareNextTurnWithContext ??\n\t\t\t(this.agent.prepareNextTurn\n\t\t\t\t? async (_turn: PrepareNextTurnContext, signal?: AbortSignal) => await this.agent.prepareNextTurn?.(signal)\n\t\t\t\t: undefined);\n\t\tthis.agent.prepareNextTurnWithContext = async (turn, signal) => {\n\t\t\tconst previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal);\n\t\t\tconst previousContext = previousSnapshot?.context ?? turn.context;\n\n\t\t\treturn {\n\t\t\t\t...previousSnapshot,\n\t\t\t\tcontext: {\n\t\t\t\t\t...previousContext,\n\t\t\t\t\tsystemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,\n\t\t\t\t\ttools: this.agent.state.tools.slice(),\n\t\t\t\t},\n\t\t\t\tmodel: this.agent.state.model,\n\t\t\t\tthinkingLevel: this.agent.state.thinkingLevel,\n\t\t\t};\n\t\t};\n\t}\n\n\t// =========================================================================\n\t// Event Subscription\n\t// =========================================================================\n\n\t/** Emit an event to all listeners */\n\tprivate _emit(event: AgentSessionEvent): void {\n\t\tfor (const l of this._eventListeners) {\n\t\t\tl(event);\n\t\t}\n\t}\n\n\tprivate _emitQueueUpdate(): void {\n\t\tthis._emit({\n\t\t\ttype: \"queue_update\",\n\t\t\tsteering: [...this._steeringMessages],\n\t\t\tfollowUp: [...this._followUpMessages],\n\t\t});\n\t}\n\n\tprivate async _emitSessionCompactFailed(event: Omit<SessionCompactFailedEvent, \"type\">): Promise<void> {\n\t\tif (this._extensionRunner.hasHandlers(\"session_compact_failed\")) {\n\t\t\tawait this._extensionRunner.emit({ type: \"session_compact_failed\", ...event });\n\t\t}\n\t}\n\n\tprivate _getIdleWaitPromise(): Promise<void> {\n\t\tif (!this._idleWaitPromise) {\n\t\t\tthis._idleWaitPromise = new Promise((resolve) => {\n\t\t\t\tthis._resolveIdleWait = resolve;\n\t\t\t});\n\t\t}\n\t\treturn this._idleWaitPromise;\n\t}\n\n\tprivate _resolveIdleWaitIfIdle(): void {\n\t\tif (this._isAgentRunActive || !this._resolveIdleWait) {\n\t\t\treturn;\n\t\t}\n\t\tconst resolve = this._resolveIdleWait;\n\t\tthis._idleWaitPromise = undefined;\n\t\tthis._resolveIdleWait = undefined;\n\t\tresolve();\n\t}\n\n\tprivate async _emitAgentSettled(): Promise<void> {\n\t\tthis._isAgentRunActive = false;\n\t\ttry {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_settled\" });\n\t\t\tthis._emit({ type: \"agent_settled\" });\n\t\t\t// El advisor mira cuando el turno se ha ASENTADO.\n\t\t\t//\n\t\t\t// Aquí y no en el manejador de eventos por dos motivos, los dos\n\t\t\t// aprendidos rompiendo cosas: `agent_settled` NO pasa por ese\n\t\t\t// manejador —su unión de tipos no lo incluye, así que el gancho era\n\t\t\t// código muerto que ninguna prueba veía—, y `agent_end`, que sí pasa,\n\t\t\t// salta una vez por INTENTO: meter entonces un mensaje `steer` se cuela\n\t\t\t// dentro del reintento y convierte uno que acaba bien en uno que acaba\n\t\t\t// mal (tres pruebas de `agent-session-retry-events`).\n\t\t\t//\n\t\t\t// Y es además lo que hay que observar: lo que el agente acabó\n\t\t\t// haciendo, no cada tentativa.\n\t\t\tthis._mirarConAdvisor();\n\t\t\t// Ada Intelligence Engine: el GoalEngine es el ÚNICO dueño de la\n\t\t\t// continuación automática del goal (pi-goal). No compite con retry/\n\t\t\t// compaction/steer. Fire-and-forget; nunca bloquea ni rompe el chat.\n\t\t\ttry {\n\t\t\t\tconst goalEngine = this._isSubagentSession ? null : this._resourceLoader.getGoalEngine?.();\n\t\t\t\tif (goalEngine) {\n\t\t\t\t\tvoid this._maybeContinueGoal(goalEngine);\n\t\t\t\t\t// Spine del grafo: sync automático del goal activo.\n\t\t\t\t\tif (this._graphSync) {\n\t\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\t\tconst goals = await goalEngine.list().catch(() => []);\n\t\t\t\t\t\t\tconst active = goals.find((g) => g.status === \"active\");\n\t\t\t\t\t\t\tif (active) {\n\t\t\t\t\t\t\t\tconst full = await goalEngine.get(active.id).catch(() => null);\n\t\t\t\t\t\t\t\tif (full) {\n\t\t\t\t\t\t\t\t\tawait this._graphSync?.syncGoal({\n\t\t\t\t\t\t\t\t\t\tid: full.id,\n\t\t\t\t\t\t\t\t\t\tgoal: full.goal,\n\t\t\t\t\t\t\t\t\t\tphase: full.phase,\n\t\t\t\t\t\t\t\t\t\ttasks: full.tasks ?? [],\n\t\t\t\t\t\t\t\t\t\tdoneCriteria: full.doneCriteria ?? [],\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._resolveIdleWaitIfIdle();\n\t\t}\n\t}\n\n\t/**\n\t * Confirmación fail-closed del lanzamiento de workflows: emite un evento y\n\t * espera respuesta humana hasta 15s; sin respuesta → NO lanzar.\n\t */\n\tprivate _confirmWorkflowLaunch(prompt: string): Promise<boolean> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis._confirmResolvers.delete(prompt);\n\t\t\t\tresolve(false);\n\t\t\t}, 15_000);\n\t\t\tthis._confirmResolvers.set(prompt, (ok: boolean) => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._confirmResolvers.delete(prompt);\n\t\t\t\tresolve(ok);\n\t\t\t});\n\t\t\tthis._emit({ type: \"notification\", message: `[workflow] ${prompt}`, level: \"warning\" });\n\t\t});\n\t}\n\n\t/** Respuesta a una confirmación pendiente (la UI la llama). */\n\tresolveWorkflowConfirmation(prompt: string, ok: boolean): boolean {\n\t\tconst resolver = this._confirmResolvers.get(prompt);\n\t\tif (!resolver) return false;\n\t\tresolver(ok);\n\t\treturn true;\n\t}\n\n\t/** Si hay un goal activo aprobado, avanza un turno del bucle. */\n\tprivate async _maybeContinueGoal(goalEngine: import(\"./workflows/goal-engine/engine.ts\").GoalEngine): Promise<void> {\n\t\tconst goals = await goalEngine.list().catch(() => []);\n\t\tconst active = goals.find((g) => g.status === \"active\");\n\t\tif (!active) return;\n\t\t// Freno por defecto del engine: si está desactivado no continúa.\n\t\tif (!this.settingsManager.getGoalEngineEnabled()) return;\n\t\t// markers anti-duplicados: solo avanzar si no hay un turno ya en curso.\n\t\tif (this._isAgentRunActive) return;\n\t\tthis._isAgentRunActive = true;\n\t\ttry {\n\t\t\tawait goalEngine.tick(active.id).catch(() => {});\n\t\t} finally {\n\t\t\tthis._isAgentRunActive = false;\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Automatic project memory (claude-mem style)\n\t// =========================================================================\n\n\t/**\n\t * Replace vault-expanded secrets with the redacted form for UI events and\n\t * persistence (the LLM still receives the expanded values via agent state).\n\t */\n\tprivate _applyVaultRedaction(event: { message: AgentMessage }): void {\n\t\tif (event.message.role !== \"user\") return;\n\t\tconst id = (event.message as { id?: string }).id;\n\t\tif (!id) return;\n\t\tconst redactedText = this._vaultRedactions.get(id);\n\t\tif (redactedText === undefined) return;\n\t\tthis._vaultRedactions.delete(id);\n\t\tevent.message = {\n\t\t\t...event.message,\n\t\t\tcontent: [{ type: \"text\", text: redactedText }],\n\t\t} as AgentMessage;\n\t}\n\n\t/** Live-index a message for the Memory Engine session search (layer 3c). */\n\tprivate _indexMessageForMemory(message: {\n\t\tid?: string;\n\t\trole?: string;\n\t\tcontent?: unknown;\n\t\ttimestamp?: string | number;\n\t}): void {\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (!memoryEngine?.isInitialized) return;\n\t\ttry {\n\t\t\tmemoryEngine.indexSessionMessage({\n\t\t\t\tid: message.id,\n\t\t\t\trole: message.role,\n\t\t\t\tcontent: message.content,\n\t\t\t\ttimestamp: message.timestamp,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Best-effort indexing.\n\t\t}\n\t}\n\n\tprivate static readonly AUTO_MEMORIZE_MIN_INTERVAL_MS = 45_000;\n\n\tprivate static readonly AUTO_MEMORIZE_SYSTEM_PROMPT = `You maintain the shared project memory of a coding project.\nThe memory is loaded into the context of every conversation of this project, so it must stay concise and durable.\n\nTask: given the CURRENT MEMORY and the LATEST CONVERSATION TURN, produce the COMPLETE new memory content in Markdown.\n- Keep everything already in the current memory that is still true.\n- Add durable facts from the turn: architecture, conventions, decisions, commands, project layout, user preferences.\n- Ignore transient details (specific code snippets, error messages, formatting fixes, small refactors).\n- If nothing durable and new was said, reply with exactly: NO_CHANGE\n- Keep it under ~600 words. Use short bullet lists.\n- Reply with ONLY the memory content (or NO_CHANGE).`;\n\n\tprivate async _maybeAutoMemorize(event: Extract<AgentEvent, { type: \"agent_end\" }>): Promise<void> {\n\t\ttry {\n\t\t\tif (!this.settingsManager.getProjectMemoryEnabled()) return;\n\t\t\tconst now = Date.now();\n\t\t\tif (now - this._lastAutoMemorizeAt < AgentSession.AUTO_MEMORIZE_MIN_INTERVAL_MS) return;\n\t\t\tconst model = this.model;\n\t\t\tif (!model) return;\n\n\t\t\t// Only turns with a real user message followed by a successful assistant reply.\n\t\t\tconst messages = event.messages;\n\t\t\tconst hasUserMessage = messages.some((m) => m.role === \"user\" && contentText(m.content, \"\").trim().length > 0);\n\t\t\tconst lastAssistant = [...messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tif (!hasUserMessage || !lastAssistant) return;\n\t\t\tconst lastAssistantText = contentText(lastAssistant.content, \"\").trim();\n\t\t\tif (!lastAssistantText) return;\n\n\t\t\t// Build a compact transcript of the turn (user + assistant + tool summaries).\n\t\t\tconst transcriptLines: string[] = [];\n\t\t\tfor (const m of messages) {\n\t\t\t\tif (m.role === \"user\") {\n\t\t\t\t\ttranscriptLines.push(`USER: ${contentText(m.content, \"\").trim().slice(0, 2000)}`);\n\t\t\t\t} else if (m.role === \"assistant\") {\n\t\t\t\t\ttranscriptLines.push(`ASSISTANT: ${contentText(m.content, \"\").trim().slice(0, 2000)}`);\n\t\t\t\t} else if (m.role === \"toolResult\") {\n\t\t\t\t\ttranscriptLines.push(`(tool result)`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst transcript = transcriptLines.join(\"\\n\").slice(0, 12_000);\n\t\t\tif (transcript.trim().length === 0) return;\n\n\t\t\tconst agentDir = this._resourceLoader.getAgentDir();\n\t\t\tconst currentMemory = loadProjectMemorySync(this._cwd, agentDir);\n\n\t\t\tconst result = await this._modelRuntime.completeSimple(model, {\n\t\t\t\tsystemPrompt: AgentSession.AUTO_MEMORIZE_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: `CURRENT MEMORY:\\n${currentMemory?.content ?? \"(none)\"}\\n\\nLATEST CONVERSATION TURN:\\n${transcript}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t} as never);\n\t\t\tconst newContent = contentText(result.content, \"\").trim();\n\t\t\tif (!newContent || newContent === \"NO_CHANGE\") return;\n\t\t\tif (newContent === currentMemory?.content) return;\n\n\t\t\tthis._lastAutoMemorizeAt = now;\n\t\t\tawait saveProjectMemory(this._cwd, agentDir, newContent);\n\t\t} catch {\n\t\t\t// Memorization must never break the conversation flow.\n\t\t}\n\t}\n\n\t// Track last assistant message for auto-compaction check\n\tprivate _lastAssistantMessage: AssistantMessage | undefined = undefined;\n\tprivate _lastAutoMemorizeAt = 0;\n\tprivate _confirmResolvers = new Map<string, (ok: boolean) => void>();\n\t/** Vault expansion redactions by user-message id (frontend + persistence). */\n\tprivate _vaultRedactions = new Map<string, string>();\n\tprivate _userMessageSeq = 0;\n\t/** Pending expansion for the user message built right after (prompt flow). */\n\tprivate _pendingVaultExpansion: { id: string; redactedText: string } | null = null;\n\n\t/** Internal handler for agent events - shared by subscribe and reconnect */\n\tprivate _handleAgentEvent = async (event: AgentEvent): Promise<void> => {\n\t\t// When a user message starts, check if it's from either queue and remove it BEFORE emitting\n\t\t// This ensures the UI sees the updated queue state\n\t\tif (event.type === \"message_start\" && event.message.role === \"user\") {\n\t\t\tthis._overflowRecoveryAttempted = false;\n\t\t\tconst messageText = contentText(event.message.content, \"\");\n\t\t\tif (messageText) {\n\t\t\t\t// Check steering queue first\n\t\t\t\tconst steeringIndex = this._steeringMessages.indexOf(messageText);\n\t\t\t\tif (steeringIndex !== -1) {\n\t\t\t\t\tthis._steeringMessages.splice(steeringIndex, 1);\n\t\t\t\t\tthis._emitQueueUpdate();\n\t\t\t\t} else {\n\t\t\t\t\t// Check follow-up queue\n\t\t\t\t\tconst followUpIndex = this._followUpMessages.indexOf(messageText);\n\t\t\t\t\tif (followUpIndex !== -1) {\n\t\t\t\t\t\tthis._followUpMessages.splice(followUpIndex, 1);\n\t\t\t\t\t\tthis._emitQueueUpdate();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Emit to extensions first\n\t\tawait this._emitExtensionEvent(event);\n\n\t\t// Notify all listeners\n\t\tthis._emit(event.type === \"agent_end\" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);\n\n\t\t// Ada Memory Engine: track user turns / corrections, run the\n\t\t// background review loop, and live-index messages for session search.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine?.isInitialized) {\n\t\t\tif (event.type === \"message_end\") {\n\t\t\t\tmemoryEngine.onMessageEnd(event.message);\n\t\t\t\tthis._indexMessageForMemory(event.message);\n\t\t\t} else if (event.type === \"turn_end\") {\n\t\t\t\tvoid memoryEngine.onTurnEnd({ message: event.message });\n\t\t\t}\n\t\t}\n\n\t\t// Libro de sesión: la llamada al modelo.\n\t\t//\n\t\t// Va en `message_end` y no en `agent_end` porque un turno puede gastar\n\t\t// varias llamadas, y \"qué modelo se usó y cuánto costó\" hay que poder\n\t\t// responderlo por llamada, no por turno.\n\t\t//\n\t\t// Se anotan también las que fallan: `motivoParada: \"error\"` es\n\t\t// justamente lo que hay que poder enseñar.\n\t\tif (event.type === \"message_end\" && event.message.role === \"assistant\") {\n\t\t\tconst asistente = event.message;\n\t\t\tthis._registro.anotarModelo(asistente);\n\t\t\t// Un turno abortado puede dejar `toolCall` sin su `tool_result`, y un\n\t\t\t// historial así lo rechazan los proveedores: la conversación entera deja\n\t\t\t// de poder reanudarse. El bucle del agente ya lo cierra, pero ESE PAQUETE\n\t\t\t// NO VIAJA en el tarball —se resuelve del registro, con la versión de\n\t\t\t// upstream—, así que se cierra también aquí, donde sí llega al cliente.\n\t\t\t// Es idempotente: si el bucle ya lo hizo, no encuentra nada que cerrar.\n\t\t\tif (asistente.stopReason === \"aborted\" || asistente.stopReason === \"error\") {\n\t\t\t\tcerrarLlamadasAbiertas(this.agent.state.messages as unknown[], asistente);\n\t\t\t}\n\n\t\t\t// Un turno VACÍO no es un turno terminado. Sin este aviso el usuario\n\t\t\t// ve silencio y lo lee como «el modelo decidió no hacer nada»: el\n\t\t\t// caso real fue `x-preview-f-free` devolviendo respuestas vacías.\n\t\t\t// El error y el abort NO entran aquí — ya se ven por su vía.\n\t\t\tif (estadoDelTurno(asistente) === \"vacio\") {\n\t\t\t\tthis._emit({ type: \"notification\", message: mensajeDeTurnoVacio(asistente), level: \"warning\" });\n\t\t\t}\n\t\t}\n\n\t\t// Automatic project memory (claude-mem style): after each completed\n\t\t// agent turn with real user interaction, distill durable facts into\n\t\t// the shared project memory. Fire-and-forget; never blocks the turn.\n\t\t// Disabled when the Memory Engine is active (its review loop replaces it).\n\t\tif (event.type === \"agent_end\") {\n\t\t\tif (!memoryEngine?.isInitialized) {\n\t\t\t\tvoid this._maybeAutoMemorize(event);\n\t\t\t}\n\t\t}\n\n\t\t// Handle session persistence\n\t\tif (event.type === \"message_end\") {\n\t\t\t// Check if this is a custom message from extensions\n\t\t\tif (event.message.role === \"custom\") {\n\t\t\t\t// Persist as CustomMessageEntry\n\t\t\t\tthis.sessionManager.appendCustomMessageEntry(\n\t\t\t\t\tevent.message.customType,\n\t\t\t\t\tevent.message.content,\n\t\t\t\t\tevent.message.display,\n\t\t\t\t\tevent.message.details,\n\t\t\t\t);\n\t\t\t} else if (\n\t\t\t\tevent.message.role === \"user\" ||\n\t\t\t\tevent.message.role === \"assistant\" ||\n\t\t\t\tevent.message.role === \"toolResult\"\n\t\t\t) {\n\t\t\t\t// Regular LLM message - persist as SessionMessageEntry. Vault\n\t\t\t\t// expansions are already redacted by _applyVaultRedaction above.\n\t\t\t\tthis.sessionManager.appendMessage(event.message);\n\t\t\t}\n\t\t\t// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere\n\n\t\t\t// Track assistant message for auto-compaction (checked on agent_end)\n\t\t\tif (event.message.role === \"assistant\") {\n\t\t\t\tthis._lastAssistantMessage = event.message;\n\n\t\t\t\tconst assistantMsg = event.message as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason !== \"error\" && assistantMsg.stopReason !== \"length\") {\n\t\t\t\t\tthis._overflowRecoveryAttempted = false;\n\t\t\t\t}\n\n\t\t\t\t// Reset retry counter immediately on successful assistant response\n\t\t\t\t// This prevents accumulation across multiple LLM calls within a turn\n\t\t\t\tif (assistantMsg.stopReason !== \"error\" && this._retryAttempt > 0) {\n\t\t\t\t\tthis._emit({\n\t\t\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tattempt: this._retryAttempt,\n\t\t\t\t\t});\n\t\t\t\t\tthis._retryAttempt = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tprivate _willRetryAfterAgentEnd(event: Extract<AgentEvent, { type: \"agent_end\" }>): boolean {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled || this._retryAttempt >= settings.maxRetries) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let i = event.messages.length - 1; i >= 0; i--) {\n\t\t\tconst message = event.messages[i];\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\treturn this._isRetryableError(message as AssistantMessage);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Find the last assistant message in agent state (including aborted ones) */\n\tprivate _findLastAssistantMessage(): AssistantMessage | undefined {\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\t\tconst msg = messages[i];\n\t\t\tif (msg.role === \"assistant\") {\n\t\t\t\treturn msg as AssistantMessage;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tprivate _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {\n\t\t// Agent-core stores the finalized message object in its state before emitting message_end.\n\t\t// SessionManager persistence happens later in _handleAgentEvent() with event.message.\n\t\t// Mutating this object in place keeps agent state, later turn/agent events, listeners,\n\t\t// and the eventual SessionManager.appendMessage(event.message) persistence in sync.\n\t\tif (target === replacement) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst targetRecord = target as unknown as Record<string, unknown>;\n\t\tfor (const key of Object.keys(targetRecord)) {\n\t\t\tdelete targetRecord[key];\n\t\t}\n\t\tObject.assign(targetRecord, replacement);\n\t}\n\n\t/** Emit extension events based on agent events */\n\tprivate async _emitExtensionEvent(event: AgentEvent): Promise<void> {\n\t\tif (event.type === \"agent_start\") {\n\t\t\tthis._turnIndex = 0;\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_start\" });\n\t\t} else if (event.type === \"agent_end\") {\n\t\t\tawait this._extensionRunner.emit({ type: \"agent_end\", messages: event.messages });\n\t\t} else if (event.type === \"turn_start\") {\n\t\t\tconst extensionEvent: TurnStartEvent = {\n\t\t\t\ttype: \"turn_start\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst extensionEvent: TurnEndEvent = {\n\t\t\t\ttype: \"turn_end\",\n\t\t\t\tturnIndex: this._turnIndex,\n\t\t\t\tmessage: event.message,\n\t\t\t\ttoolResults: event.toolResults,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t\tthis._turnIndex++;\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tthis._applyVaultRedaction(event);\n\t\t\tconst extensionEvent: MessageStartEvent = {\n\t\t\t\ttype: \"message_start\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_update\") {\n\t\t\tconst extensionEvent: MessageUpdateEvent = {\n\t\t\t\ttype: \"message_update\",\n\t\t\t\tmessage: event.message,\n\t\t\t\tassistantMessageEvent: event.assistantMessageEvent,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tthis._applyVaultRedaction(event);\n\t\t\tconst extensionEvent: MessageEndEvent = {\n\t\t\t\ttype: \"message_end\",\n\t\t\t\tmessage: event.message,\n\t\t\t};\n\t\t\tconst replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);\n\t\t\tif (replacement) {\n\t\t\t\t// Untyped extension handlers can return messages with null/missing content;\n\t\t\t\t// normalize so it never enters agent state or session history.\n\t\t\t\tconst normalized =\n\t\t\t\t\t(replacement.role === \"user\" ||\n\t\t\t\t\t\treplacement.role === \"assistant\" ||\n\t\t\t\t\t\treplacement.role === \"toolResult\" ||\n\t\t\t\t\t\treplacement.role === \"custom\") &&\n\t\t\t\t\treplacement.content == null\n\t\t\t\t\t\t? ({ ...replacement, content: [] } as AgentMessage)\n\t\t\t\t\t\t: replacement;\n\t\t\t\tthis._replaceMessageInPlace(event.message, normalized);\n\t\t\t}\n\t\t} else if (event.type === \"tool_execution_start\") {\n\t\t\tconst extensionEvent: ToolExecutionStartEvent = {\n\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_update\") {\n\t\t\tconst extensionEvent: ToolExecutionUpdateEvent = {\n\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\targs: event.args,\n\t\t\t\tpartialResult: event.partialResult,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst extensionEvent: ToolExecutionEndEvent = {\n\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\ttoolName: event.toolName,\n\t\t\t\tresult: event.result,\n\t\t\t\tisError: event.isError,\n\t\t\t};\n\t\t\tawait this._extensionRunner.emit(extensionEvent);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t * Session persistence is handled internally (saves messages on message_end).\n\t * Multiple listeners can be added. Returns unsubscribe function for this listener.\n\t */\n\tsubscribe(listener: AgentSessionEventListener): () => void {\n\t\tthis._eventListeners.push(listener);\n\n\t\t// Return unsubscribe function for this specific listener\n\t\treturn () => {\n\t\t\tconst index = this._eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis._eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/** Disconnect from agent events during disposal. */\n\tprivate _disconnectFromAgent(): void {\n\t\tif (this._unsubscribeAgent) {\n\t\t\tthis._unsubscribeAgent();\n\t\t\tthis._unsubscribeAgent = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Remove all listeners and disconnect from agent.\n\t * Call this when completely done with the session.\n\t */\n\tdispose(): void {\n\t\ttry {\n\t\t\tthis.abortRetry();\n\t\t\tthis.abortCompaction();\n\t\t\tthis.abortBranchSummary();\n\t\t\tthis.abortBash();\n\t\t\tthis.agent.abort();\n\t\t} catch {\n\t\t\t// Dispose must succeed even if an abort hook throws.\n\t\t}\n\n\t\t// Ada Memory Engine: persist the session index (best-effort).\n\t\ttry {\n\t\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\t\tif (memoryEngine?.isInitialized) {\n\t\t\t\tvoid memoryEngine.onShutdown();\n\t\t\t}\n\t\t} catch {\n\t\t\t// Dispose must succeed even if memory persistence fails.\n\t\t}\n\n\t\tthis._extensionRunner.invalidate(\n\t\t\t\"This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().\",\n\t\t);\n\t\tthis._disconnectFromAgent();\n\t\tthis._eventListeners = [];\n\t\tcleanupSessionResources(this.sessionId);\n\t}\n\n\t// =========================================================================\n\t// Read-only State Access\n\t// =========================================================================\n\n\t/** Full agent state */\n\tget state(): AgentState {\n\t\treturn this.agent.state;\n\t}\n\n\t/** Current model (may be undefined if not yet selected) */\n\tget model(): Model<any> | undefined {\n\t\treturn this.agent.state.model;\n\t}\n\n\t/** Current thinking level */\n\tget thinkingLevel(): ThinkingLevel {\n\t\treturn this.agent.state.thinkingLevel;\n\t}\n\n\t/** Whether the session is currently processing an agent run or post-run continuation. */\n\tget isStreaming(): boolean {\n\t\treturn this._isAgentRunActive;\n\t}\n\n\t/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */\n\tget isIdle(): boolean {\n\t\treturn !this._isAgentRunActive;\n\t}\n\n\t/** Current effective system prompt (includes any per-turn extension modifications) */\n\tget systemPrompt(): string {\n\t\treturn this.agent.state.systemPrompt;\n\t}\n\n\t/** Current retry attempt (0 if not retrying) */\n\tget retryAttempt(): number {\n\t\treturn this._retryAttempt;\n\t}\n\n\t/**\n\t * Get the names of currently active tools.\n\t * Returns the names of tools currently set on the agent.\n\t */\n\tgetActiveToolNames(): string[] {\n\t\treturn this.agent.state.tools.map((t) => t.name);\n\t}\n\n\t/**\n\t * Get all configured tools with name, description, parameter schema, prompt guidelines, and source metadata.\n\t */\n\tgetAllTools(): ToolInfo[] {\n\t\treturn Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({\n\t\t\tname: definition.name,\n\t\t\tdescription: definition.description,\n\t\t\tparameters: definition.parameters,\n\t\t\tpromptGuidelines: definition.promptGuidelines,\n\t\t\tsourceInfo,\n\t\t}));\n\t}\n\n\tgetToolDefinition(name: string): ToolDefinition | undefined {\n\t\treturn this._toolDefinitions.get(name)?.definition;\n\t}\n\n\t/**\n\t * Set active tools by name.\n\t * Only tools in the registry can be enabled. Unknown tool names are ignored.\n\t * Also rebuilds the system prompt to reflect the new tool set.\n\t * Changes take effect on the next agent turn.\n\t */\n\tsetActiveToolsByName(toolNames: string[]): void {\n\t\tconst tools: AgentTool[] = [];\n\t\tconst validToolNames: string[] = [];\n\t\tfor (const name of toolNames) {\n\t\t\tconst tool = this._toolRegistry.get(name);\n\t\t\tif (tool) {\n\t\t\t\ttools.push(tool);\n\t\t\t\tvalidToolNames.push(name);\n\t\t\t}\n\t\t}\n\t\tthis.agent.state.tools = tools;\n\n\t\t// Rebuild base system prompt with new tool set\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/**\n\t * Rebuild the base system prompt (public) — used after settings changes\n\t * that affect prompt content (e.g. memory injection mode).\n\t */\n\trebuildSystemPrompt(): void {\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;\n\t}\n\n\t/** Whether compaction or branch summarization is currently running */\n\tget isCompacting(): boolean {\n\t\treturn (\n\t\t\tthis._autoCompactionAbortController !== undefined ||\n\t\t\tthis._compactionAbortController !== undefined ||\n\t\t\tthis._branchSummaryAbortController !== undefined\n\t\t);\n\t}\n\n\t/** All messages including custom types like BashExecutionMessage */\n\tget messages(): AgentMessage[] {\n\t\treturn this.agent.state.messages;\n\t}\n\n\t/** Current steering mode */\n\tget steeringMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.agent.steeringMode;\n\t}\n\n\t/** Current follow-up mode */\n\tget followUpMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.agent.followUpMode;\n\t}\n\n\t/** Current session file path, or undefined if sessions are disabled */\n\tget sessionFile(): string | undefined {\n\t\treturn this.sessionManager.getSessionFile();\n\t}\n\n\t/** Current session ID */\n\tget sessionId(): string {\n\t\treturn this.sessionManager.getSessionId();\n\t}\n\n\t/** Current session display name, if set */\n\tget sessionName(): string | undefined {\n\t\treturn this.sessionManager.getSessionName();\n\t}\n\n\t/** Scoped models for cycling (from --models flag) */\n\tget scopedModels(): ReadonlyArray<{ model: Model<any>; thinkingLevel?: ThinkingLevel }> {\n\t\treturn this._scopedModels;\n\t}\n\n\t/** Update scoped models for cycling */\n\tsetScopedModels(scopedModels: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>): void {\n\t\tthis._scopedModels = scopedModels;\n\t}\n\n\t/** File-based prompt templates */\n\tget promptTemplates(): ReadonlyArray<PromptTemplate> {\n\t\treturn this._resourceLoader.getPrompts().prompts;\n\t}\n\n\tprivate _normalizePromptSnippet(text: string | undefined): string | undefined {\n\t\tif (!text) return undefined;\n\t\tconst oneLine = text\n\t\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t\t.replace(/\\s+/g, \" \")\n\t\t\t.trim();\n\t\treturn oneLine.length > 0 ? oneLine : undefined;\n\t}\n\n\tprivate _normalizePromptGuidelines(guidelines: string[] | undefined): string[] {\n\t\tif (!guidelines || guidelines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst unique = new Set<string>();\n\t\tfor (const guideline of guidelines) {\n\t\t\tconst normalized = guideline.trim();\n\t\t\tif (normalized.length > 0) {\n\t\t\t\tunique.add(normalized);\n\t\t\t}\n\t\t}\n\t\treturn Array.from(unique);\n\t}\n\n\tprivate _rebuildSystemPrompt(toolNames: string[]): string {\n\t\tconst validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));\n\t\tconst toolSnippets: Record<string, string> = {};\n\t\tconst promptGuidelines: string[] = [];\n\t\tfor (const name of validToolNames) {\n\t\t\tconst snippet = this._toolPromptSnippets.get(name);\n\t\t\tif (snippet) {\n\t\t\t\ttoolSnippets[name] = snippet;\n\t\t\t}\n\n\t\t\tconst toolGuidelines = this._toolPromptGuidelines.get(name);\n\t\t\tif (toolGuidelines) {\n\t\t\t\tpromptGuidelines.push(...toolGuidelines);\n\t\t\t}\n\t\t}\n\n\t\tconst loaderSystemPrompt = this._resourceLoader.getSystemPrompt();\n\t\tconst loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();\n\t\t// Ada Intelligence Engine: PERSONALIDAD SIEMPRE presente (sesión principal).\n\t\t// No depende de que el modelo lea la skill: el árbol de decisión y los\n\t\t// procedimientos van inline en el prompt, cada turno.\n\t\tlet enginePrompt: string | undefined;\n\t\tif (\n\t\t\t!this._isSubagentSession &&\n\t\t\t(this.settingsManager.getGoalEngineEnabled() || this.settingsManager.getWorkflowsEnabled())\n\t\t) {\n\t\t\tenginePrompt = buildEnginePersonalityBlock();\n\t\t}\n\t\tconst appendSystemPrompt = enginePrompt\n\t\t\t? [enginePrompt, ...(loaderAppendSystemPrompt.length > 0 ? [loaderAppendSystemPrompt.join(\"\\n\\n\")] : [])].join(\n\t\t\t\t\t\"\\n\\n\",\n\t\t\t\t)\n\t\t\t: loaderAppendSystemPrompt.length > 0\n\t\t\t\t? loaderAppendSystemPrompt.join(\"\\n\\n\")\n\t\t\t\t: undefined;\n\t\tconst loadedSkills = this._resourceLoader.getSkills().skills;\n\t\tconst loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;\n\n\t\tthis._baseSystemPromptOptions = {\n\t\t\t// Modo plan: el prompt gana la sección que dice que el árbol es de solo\n\t\t\t// lectura. Se deriva con la MISMA función que usa el guardián\n\t\t\t// (`RegistroDeSesion.antesDeLlamada` → `decidir({ modo: nivelDesdeConfig(…) })`)\n\t\t\t// y no leyendo `planOnly` a mano, para que el prompt no pueda decir una\n\t\t\t// cosa y la política otra el día que `nivelDesdeConfig` cambie de\n\t\t\t// criterio. Va como primer campo a propósito: así este cambio cae dentro\n\t\t\t// del hunk de sincronización que ya había justo arriba en vez de abrir\n\t\t\t// uno nuevo (el presupuesto de fork está a 118/118, medido).\n\t\t\tmodoPlan: nivelDesdeConfig(this.settingsManager.getEngineConfig()) === \"plan\",\n\t\t\ttoolsDenegadas: leerDenegadas(this.settingsManager.getEngineConfig()),\n\t\t\tcwd: this._cwd,\n\t\t\tskills: loadedSkills,\n\t\t\tcontextFiles: loadedContextFiles,\n\t\t\tcustomPrompt: loaderSystemPrompt,\n\t\t\tappendSystemPrompt,\n\t\t\tselectedTools: validToolNames,\n\t\t\ttoolSnippets,\n\t\t\tpromptGuidelines,\n\t\t};\n\t\treturn buildSystemPrompt(this._baseSystemPromptOptions);\n\t}\n\n\t// =========================================================================\n\t// Prompting\n\t// =========================================================================\n\n\tprivate async _runAgentPrompt(messages: AgentMessage | AgentMessage[]): Promise<void> {\n\t\tthis._isAgentRunActive = true;\n\t\ttry {\n\t\t\tawait this.agent.prompt(messages);\n\t\t\twhile (await this._handlePostAgentRun()) {\n\t\t\t\tawait this.agent.continue();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._systemPromptOverride = undefined;\n\t\t\tthis._flushPendingBashMessages();\n\t\t\tawait this._emitAgentSettled();\n\t\t}\n\t}\n\n\tprivate async _handlePostAgentRun(): Promise<boolean> {\n\t\tconst msg = this._lastAssistantMessage;\n\t\tthis._lastAssistantMessage = undefined;\n\t\tif (!msg) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._isRetryableError(msg) && (await this._prepareRetry(msg))) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (msg.stopReason === \"error\" && this._retryAttempt > 0) {\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt: this._retryAttempt,\n\t\t\t\tfinalError: msg.errorMessage,\n\t\t\t});\n\t\t\tthis._retryAttempt = 0;\n\t\t}\n\n\t\tif (await this._checkCompaction(msg)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// The agent loop drains both queues before emitting agent_end. Any messages\n\t\t// here were queued by agent_end extension handlers and need a continuation.\n\t\treturn this.agent.hasQueuedMessages();\n\t}\n\n\t/**\n\t * Send a prompt to the agent.\n\t * - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming\n\t * - Expands file-based prompt templates by default\n\t * - During streaming, queues via steer() or followUp() based on streamingBehavior option\n\t * - Validates model and API key before sending (when not streaming)\n\t * @throws Error if streaming and no streamingBehavior specified\n\t * @throws Error if no model selected or no API key available (when not streaming)\n\t */\n\tasync prompt(text: string, options?: PromptOptions): Promise<void> {\n\t\t// Frontera de turno: agrupa las escrituras de este prompt para `undo`, y\n\t\t// dispara el hook `user_prompt_submit`. Las dos cosas necesitan el mismo\n\t\t// momento exacto, así que comparten sitio.\n\t\tabrirTurno(this._resourceLoader.getAgentDir(), this._cwd, Date.now());\n\t\tvoid dispararHooks(this.settingsManager.getEngineConfig(), \"user_prompt_submit\", \"user_prompt_submit\", {\n\t\t\tsessionId: this.sessionId,\n\t\t\tcwd: this._cwd,\n\t\t\tprompt: text,\n\t\t}).catch(() => {});\n\t\t// Presupuesto de razonamiento por dificultad del turno. Aquí y no más\n\t\t// abajo porque tiene que decidirse ANTES de construir la petición, y con\n\t\t// el texto tal cual lo escribió el usuario. No llama al modelo.\n\t\t// Solo fuera de streaming: un mensaje de dirección corto («no, para»)\n\t\t// no debe bajar el presupuesto de la tarea larga que ya está en marcha.\n\t\tif (!this.isStreaming) {\n\t\t\tajustarPresupuestoDeRazonamiento(this, text, {\n\t\t\t\tnivelActual: this.thinkingLevel,\n\t\t\t\tdisponibles: this.getAvailableThinkingLevels(),\n\t\t\t\tfijar: (nivel) => {\n\t\t\t\t\tthis.setThinkingLevel(nivel);\n\t\t\t\t\treturn this.thinkingLevel;\n\t\t\t\t},\n\t\t\t\tavisar: (mensaje) => this._emit({ type: \"notification\", message: mensaje, level: \"info\" }),\n\t\t\t});\n\t\t}\n\t\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\t\tconst preflightResult = options?.preflightResult;\n\t\tlet messages: AgentMessage[] | undefined;\n\n\t\ttry {\n\t\t\t// Handle extension commands first (execute immediately, even during streaming)\n\t\t\t// Extension commands manage their own LLM interaction via pi.sendMessage()\n\t\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\t\tconst handled = await this._tryExecuteExtensionCommand(text);\n\t\t\t\tif (handled) {\n\t\t\t\t\t// Extension command executed, no prompt to send\n\t\t\t\t\tpreflightResult?.(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this._compactionAbortController !== undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\t\tlet currentText = text;\n\t\t\tlet currentImages = options?.images;\n\t\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\t\tcurrentText,\n\t\t\t\t\tcurrentImages,\n\t\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t\t);\n\t\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\t\tpreflightResult?.(true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\t\tlet expandedText = currentText;\n\t\t\tif (expandPromptTemplates) {\n\t\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t\t}\n\n\t\t\t// Enterprise security: detect sensitive data in the raw text (warning\n\t\t\t// + audit) and redact it BEFORE it reaches the LLM, the session file\n\t\t\t// or the chat transcript. The user sees censored content; the raw\n\t\t\t// secret is never persisted anywhere.\n\t\t\t// The engine exists whenever the SDK is built with the memory module;\n\t\t\t// detection + redaction only need config (not the initialized stores),\n\t\t\t// so they are ALWAYS applied — a failed init must never disable them.\n\t\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\t\tif (memoryEngine) {\n\t\t\t\tmemoryEngine.checkUserTextForSensitive(expandedText);\n\t\t\t\texpandedText = redactSensitive(expandedText, memoryEngine.privacyLevel);\n\t\t\t}\n\n\t\t\t// Secure Vault: expand @vault:<name> references so the LLM can use\n\t\t\t// secrets without them ever being persisted. The redacted text is\n\t\t\t// kept for session persistence (applied in message_end).\n\t\t\tconst vaultManager = this._resourceLoader.getVaultManager();\n\t\t\tif (vaultManager) {\n\t\t\t\tconst expansion = expandVaultRefs(expandedText, vaultManager);\n\t\t\t\tif (expansion.expanded.length > 0) {\n\t\t\t\t\tthis._pendingVaultExpansion = {\n\t\t\t\t\t\tid: `usr-vault-${++this._userMessageSeq}`,\n\t\t\t\t\t\tredactedText: expandedText,\n\t\t\t\t\t};\n\t\t\t\t\texpandedText = expansion.text;\n\t\t\t\t\t// Audit the vault usage (names only — never the values).\n\t\t\t\t\tmemoryEngine?.recordAudit({\n\t\t\t\t\t\taction: \"vault-use\",\n\t\t\t\t\t\tsource: \"user\",\n\t\t\t\t\t\ttarget: \"vault\",\n\t\t\t\t\t\tscope: vaultManager.status().vaultPath,\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\treason: expansion.expanded.join(\", \"),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If streaming, queue via steer() or followUp() based on option\n\t\t\tif (this.isStreaming) {\n\t\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t\t} else {\n\t\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t\t}\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Flush any pending bash messages before the new prompt\n\t\t\tthis._flushPendingBashMessages();\n\n\t\t\t// Validate model\n\t\t\tif (!this.model) {\n\t\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t\t}\n\n\t\t\tconst hasConfiguredAuth =\n\t\t\t\tthis._modelRuntime.hasConfiguredAuth(this.model.provider) ||\n\t\t\t\t(await this._modelRuntime.checkAuth(this.model.provider)) !== undefined;\n\t\t\tif (!hasConfiguredAuth) {\n\t\t\t\tconst isOAuth = this._modelRuntime.isUsingOAuth(this.model.provider);\n\t\t\t\tif (isOAuth) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t\t}\n\n\t\t\t// Check if we need to compact before sending (catches aborted responses).\n\t\t\t// The user's new prompt is sent below, so do not call agent.continue() here.\n\t\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\t\tif (lastAssistant) {\n\t\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t\t}\n\n\t\t\t// Build messages array (custom message if any, then user message)\n\t\t\tmessages = [];\n\n\t\t\t// Add user message\n\t\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\t\tif (currentImages) {\n\t\t\t\tuserContent.push(...currentImages);\n\t\t\t}\n\t\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\t\tthis._pendingVaultExpansion = null;\n\t\t\tconst userMessage: AgentMessage = {\n\t\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: userContent,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as AgentMessage;\n\t\t\tif (vaultExpansion) {\n\t\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t\t}\n\t\t\tmessages.push(userMessage);\n\n\t\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\t\tmessages.push(msg);\n\t\t\t}\n\t\t\tthis._pendingNextTurnMessages = [];\n\n\t\t\t// Emit before_agent_start extension event\n\t\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\t\texpandedText,\n\t\t\t\tcurrentImages,\n\t\t\t\tthis._baseSystemPrompt,\n\t\t\t\tthis._baseSystemPromptOptions,\n\t\t\t);\n\t\t\t// Add all custom messages from extensions\n\t\t\tif (result?.messages) {\n\t\t\t\tfor (const msg of result.messages) {\n\t\t\t\t\tmessages.push({\n\t\t\t\t\t\trole: \"custom\",\n\t\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\t\t// Untyped extensions can pass null/missing content; normalize at ingestion.\n\t\t\t\t\t\tcontent: msg.content ?? [],\n\t\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Apply extension-modified system prompt, or reset to base\n\t\t\tif (result?.systemPrompt !== undefined) {\n\t\t\t\tthis._systemPromptOverride = result.systemPrompt;\n\t\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t\t} else {\n\t\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\t\tthis._systemPromptOverride = undefined;\n\t\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tpreflightResult?.(false);\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (!messages) {\n\t\t\treturn;\n\t\t}\n\n\t\tpreflightResult?.(true);\n\t\tawait this._runAgentPrompt(messages);\n\t}\n\n\t/**\n\t * Try to execute an extension command. Returns true if command was found and executed.\n\t */\n\tprivate async _tryExecuteExtensionCommand(text: string): Promise<boolean> {\n\t\t// Parse command name and args\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\t\tif (!command) return false;\n\n\t\t// Get command context from extension runner (includes session control methods)\n\t\tconst ctx = this._extensionRunner.createCommandContext();\n\n\t\ttry {\n\t\t\tawait command.handler(args, ctx);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Emit error via extension runner\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: `command:${commandName}`,\n\t\t\t\tevent: \"command\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Expand skill commands (/skill:name args) to their full content.\n\t * Returns the expanded text, or the original text if not a skill command or skill not found.\n\t * Emits errors via extension runner if file read fails.\n\t */\n\tprivate _expandSkillCommand(text: string): string {\n\t\tif (!text.startsWith(\"/skill:\")) return text;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\t\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\t\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\t\tif (!skill) return text; // Unknown skill, pass through\n\n\t\ttry {\n\t\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\t\tconst body = stripFrontmatter(content).trim();\n\t\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t\t} catch (err) {\n\t\t\t// Emit error like extension commands do\n\t\t\tthis._extensionRunner.emitError({\n\t\t\t\textensionPath: skill.filePath,\n\t\t\t\tevent: \"skill_expansion\",\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t\treturn text; // Return original on error\n\t\t}\n\t}\n\n\t/**\n\t * Queue a steering message while the agent is running.\n\t * Delivered after the current assistant turn finishes executing its tool calls,\n\t * before the next LLM call.\n\t * Expands skill commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync steer(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\n\t\t// Expand skill commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueSteer(expandedText, images);\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t * Delivered only when agent has no more tool calls or steering messages.\n\t * Expands skill commands and prompt templates. Errors on extension commands.\n\t * @param images Optional image attachments to include with the message\n\t * @throws Error if text is an extension command\n\t */\n\tasync followUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\t// Check for extension commands (cannot be queued)\n\t\tif (text.startsWith(\"/\")) {\n\t\t\tthis._throwIfExtensionCommand(text);\n\t\t}\n\n\t\t// Expand skill commands and prompt templates\n\t\tlet expandedText = this._expandSkillCommand(text);\n\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\t\tawait this._queueFollowUp(expandedText, images);\n\t}\n\n\t/**\n\t * Internal: Queue a steering message (already expanded, no extension command check).\n\t */\n\tprivate async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {\n\t\tthis._steeringMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\tthis._pendingVaultExpansion = null;\n\t\tconst message: AgentMessage = {\n\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t} as AgentMessage;\n\t\tif (vaultExpansion) {\n\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t}\n\t\tthis.agent.steer(message);\n\t}\n\n\t/**\n\t * Internal: Queue a follow-up message (already expanded, no extension command check).\n\t */\n\tprivate async _queueFollowUp(text: string, images?: ImageContent[]): Promise<void> {\n\t\tthis._followUpMessages.push(text);\n\t\tthis._emitQueueUpdate();\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (images) {\n\t\t\tcontent.push(...images);\n\t\t}\n\t\tconst vaultExpansion = this._pendingVaultExpansion;\n\t\tthis._pendingVaultExpansion = null;\n\t\tconst message: AgentMessage = {\n\t\t\t...(vaultExpansion ? { id: vaultExpansion.id } : {}),\n\t\t\trole: \"user\",\n\t\t\tcontent,\n\t\t\ttimestamp: Date.now(),\n\t\t} as AgentMessage;\n\t\tif (vaultExpansion) {\n\t\t\tthis._vaultRedactions.set(vaultExpansion.id, vaultExpansion.redactedText);\n\t\t}\n\t\tthis.agent.followUp(message);\n\t\treturn;\n\t}\n\n\t/**\n\t * Throw an error if the text is an extension command.\n\t */\n\tprivate _throwIfExtensionCommand(text: string): void {\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\tconst command = this._extensionRunner.getCommand(commandName);\n\n\t\tif (command) {\n\t\t\tthrow new Error(\n\t\t\t\t`Extension command \"/${commandName}\" cannot be queued. Use prompt() or execute the command when not streaming.`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Send a custom message to the session. Creates a CustomMessageEntry.\n\t *\n\t * Handles three cases:\n\t * - Streaming: queues message, processed when loop pulls from queue\n\t * - Not streaming + triggerTurn: appends to state/session, starts new turn\n\t * - Not streaming + no trigger: appends to state/session, no turn\n\t *\n\t * @param message Custom message with customType, content, display, details\n\t * @param options.triggerTurn If true and not streaming, triggers a new LLM turn\n\t * @param options.deliverAs Delivery mode: \"steer\", \"followUp\", or \"nextTurn\"\n\t */\n\tasync sendCustomMessage<T = unknown>(\n\t\tmessage: Pick<CustomMessage<T>, \"customType\" | \"content\" | \"display\" | \"details\">,\n\t\toptions?: { triggerTurn?: boolean; deliverAs?: \"steer\" | \"followUp\" | \"nextTurn\" },\n\t): Promise<void> {\n\t\tconst appMessage = {\n\t\t\trole: \"custom\" as const,\n\t\t\tcustomType: message.customType,\n\t\t\t// Untyped extensions can pass null/missing content; normalize at ingestion.\n\t\t\tcontent: message.content ?? [],\n\t\t\tdisplay: message.display,\n\t\t\tdetails: message.details,\n\t\t\ttimestamp: Date.now(),\n\t\t} satisfies CustomMessage<T>;\n\t\tif (options?.deliverAs === \"nextTurn\") {\n\t\t\tthis._pendingNextTurnMessages.push(appMessage);\n\t\t} else if (this.isStreaming && options?.triggerTurn !== false) {\n\t\t\tif (options?.deliverAs === \"followUp\") {\n\t\t\t\tthis.agent.followUp(appMessage);\n\t\t\t} else {\n\t\t\t\tthis.agent.steer(appMessage);\n\t\t\t}\n\t\t} else if (options?.triggerTurn) {\n\t\t\tawait this._runAgentPrompt(appMessage);\n\t\t} else {\n\t\t\tthis.agent.state.messages.push(appMessage);\n\t\t\tthis.sessionManager.appendCustomMessageEntry(\n\t\t\t\tmessage.customType,\n\t\t\t\tmessage.content,\n\t\t\t\tmessage.display,\n\t\t\t\tmessage.details,\n\t\t\t);\n\t\t\tthis._emit({ type: \"message_start\", message: appMessage });\n\t\t\tthis._emit({ type: \"message_end\", message: appMessage });\n\t\t}\n\t}\n\n\t/**\n\t * Send a user message to the agent. Always triggers a turn.\n\t * When the agent is streaming, use deliverAs to specify how to queue the message.\n\t *\n\t * @param content User message content (string or content array)\n\t * @param options.deliverAs Delivery mode when streaming: \"steer\" or \"followUp\"\n\t * @param options.expandPromptTemplates Whether to dispatch extension commands and expand skill commands and prompt templates. Default: false.\n\t */\n\tasync sendUserMessage(\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\toptions?: { deliverAs?: \"steer\" | \"followUp\"; expandPromptTemplates?: boolean },\n\t): Promise<void> {\n\t\t// Normalize content to text string + optional images\n\t\tlet text: string;\n\t\tlet images: ImageContent[] | undefined;\n\n\t\tif (typeof content === \"string\") {\n\t\t\ttext = content;\n\t\t} else {\n\t\t\tconst textParts: string[] = [];\n\t\t\timages = [];\n\t\t\tfor (const part of content) {\n\t\t\t\tif (part.type === \"text\") {\n\t\t\t\t\ttextParts.push(part.text);\n\t\t\t\t} else {\n\t\t\t\t\timages.push(part);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttext = textParts.join(\"\\n\");\n\t\t\tif (images.length === 0) images = undefined;\n\t\t}\n\n\t\t// ── Optimizador autónomo de prompt ───────────────────────────────────\n\t\t// Mejora la petición del usuario (claridad, contexto, formato, criterios,\n\t\t// alcance) ANTES de que llegue al modelo. Fail-open: cualquier error o\n\t\t// timeout usa el prompt original — nunca degrada la experiencia.\n\t\tlet finalText = text;\n\t\tif (this._promptOptimizerMode !== \"off\" && typeof text === \"string\" && !text.startsWith(\"/\")) {\n\t\t\ttry {\n\t\t\t\tconst result = await this._runPromptOptimizer(text);\n\t\t\t\tif (result) {\n\t\t\t\t\tif (result.decision === \"clarify\" && this._promptOptimizerMode === \"clarify\") {\n\t\t\t\t\t\t// Preguntar primero y esperar respuesta; no enviamos aún.\n\t\t\t\t\t\tthis._pendingClarification = { original: text, questions: result.questions };\n\t\t\t\t\t\tthis._emit({\n\t\t\t\t\t\t\ttype: \"prompt_optimization\",\n\t\t\t\t\t\t\tdecision: \"clarify\",\n\t\t\t\t\t\t\toriginal: text,\n\t\t\t\t\t\t\tquestions: result.questions,\n\t\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst questions = result.questions.map((q, i) => `${i + 1}. ${q}`).join(\"\\n\");\n\t\t\t\t\t\tawait this.sendCustomMessage<{ q: string[] }>(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcustomType: \"clarify-questions\",\n\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\ttext: `🔎 Para dejar tu petición sin huecos, necesito aclarar:\\n${questions}\\n\\nResponde a las preguntas y continuaré.`,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\t\tdetails: { q: result.questions },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ triggerTurn: false },\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (result.enhanced && result.enhanced !== text) {\n\t\t\t\t\t\tfinalText = result.enhanced;\n\t\t\t\t\t\tthis._emit({\n\t\t\t\t\t\t\ttype: \"prompt_optimization\",\n\t\t\t\t\t\t\tdecision: \"enhance\",\n\t\t\t\t\t\t\toriginal: text,\n\t\t\t\t\t\t\tenhanced: result.enhanced,\n\t\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Mensaje persistente en el chat (CLI + desktop): muestra el\n\t\t\t\t\t\t// antes → después para que el usuario vea la mejora.\n\t\t\t\t\t\tvoid this.sendCustomMessage<{ original: string; summary: string }>(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcustomType: \"prompt-optimization\",\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: result.enhanced }],\n\t\t\t\t\t\t\t\tdisplay: true,\n\t\t\t\t\t\t\t\tdetails: { original: text, summary: result.summary },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ triggerTurn: false },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fail-open: continuamos con el prompt original\n\t\t\t}\n\t\t}\n\n\t\tawait this.prompt(finalText, {\n\t\t\texpandPromptTemplates: options?.expandPromptTemplates ?? false,\n\t\t\tstreamingBehavior: options?.deliverAs,\n\t\t\timages,\n\t\t\tsource: \"extension\",\n\t\t});\n\t}\n\n\t/**\n\t * Ejecuta el optimizador con el modelo de la sesión. Timeout de 12s,\n\t * nunca lanza (devuelve undefined → fail-open).\n\t */\n\tprivate async _runPromptOptimizer(\n\t\ttext: string,\n\t): Promise<\n\t\t| { decision: \"clarify\"; questions: string[]; enhanced: string; summary: string }\n\t\t| { decision: \"enhance\"; questions: string[]; enhanced: string; summary: string }\n\t\t| undefined\n\t> {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\t\ttry {\n\t\t\tconst answers = this._pendingClarification?.answers;\n\t\t\tconst result = await Promise.race([\n\t\t\t\toptimizeUserPrompt(\n\t\t\t\t\t{\n\t\t\t\t\t\ttext,\n\t\t\t\t\t\t// En modo auto nunca bloqueamos al usuario: si el LLM pide\n\t\t\t\t\t\t// clarificar, forzamos una mejora con supuestos razonables.\n\t\t\t\t\t\tforceEnhance: this._promptOptimizerMode === \"auto\",\n\t\t\t\t\t\tcontext: {\n\t\t\t\t\t\t\tprojectDir: this._cwd,\n\t\t\t\t\t\t\t// Si el usuario ya respondió a las preguntas de la ronda\n\t\t\t\t\t\t\t// anterior, se las pasamos al optimizador como contexto.\n\t\t\t\t\t\t\trecentMessages: answers?.length\n\t\t\t\t\t\t\t\t? [...answers, ...(this._recentUserPrompts(3) ?? [])]\n\t\t\t\t\t\t\t\t: this._recentUserPrompts(3),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tasync (systemPrompt, userContent) => {\n\t\t\t\t\t\tconst completed = await this._modelRuntime.completeSimple(model, {\n\t\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\t\tmessages: [{ role: \"user\", content: userContent }],\n\t\t\t\t\t\t\tsignal: AbortSignal.timeout(20_000),\n\t\t\t\t\t\t} as never);\n\t\t\t\t\t\treturn contentText(completed.content, \"\").trim();\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\tnew Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), 25_000)),\n\t\t\t]);\n\t\t\tif (!result) return undefined;\n\t\t\tthis._pendingClarification = undefined;\n\t\t\treturn result;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/** Últimos prompts de usuario (para dar contexto al optimizador). */\n\tprivate _recentUserPrompts(max: number): string[] {\n\t\tconst out: string[] = [];\n\t\tconst messages = this.agent.state.messages;\n\t\tfor (let i = messages.length - 1; i >= 0 && out.length < max; i--) {\n\t\t\tconst m = messages[i];\n\t\t\tif (m.role === \"user\") {\n\t\t\t\tconst t = contentText(m.content, \"\");\n\t\t\t\tif (t.trim()) out.unshift(t.slice(0, 500));\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Ajusta el modo del optimizador autónomo de prompts. */\n\tsetPromptOptimizerMode(mode: \"off\" | \"auto\" | \"clarify\"): void {\n\t\tthis._promptOptimizerMode = mode;\n\t}\n\n\t/** Modo actual del optimizador autónomo de prompts. */\n\tgetPromptOptimizerMode(): \"off\" | \"auto\" | \"clarify\" {\n\t\treturn this._promptOptimizerMode;\n\t}\n\n\t/**\n\t * Clear all queued messages and return them.\n\t * Useful for restoring to editor when user aborts.\n\t * @returns Object with steering and followUp arrays\n\t */\n\tclearQueue(): { steering: string[]; followUp: string[] } {\n\t\tconst steering = [...this._steeringMessages];\n\t\tconst followUp = [...this._followUpMessages];\n\t\tthis._steeringMessages = [];\n\t\tthis._followUpMessages = [];\n\t\tthis.agent.clearAllQueues();\n\t\tthis._emitQueueUpdate();\n\t\treturn { steering, followUp };\n\t}\n\n\t/** Number of pending messages (includes both steering and follow-up) */\n\tget pendingMessageCount(): number {\n\t\treturn this._steeringMessages.length + this._followUpMessages.length;\n\t}\n\n\t/** Get pending steering messages (read-only) */\n\tgetSteeringMessages(): readonly string[] {\n\t\treturn this._steeringMessages;\n\t}\n\n\t/** Get pending follow-up messages (read-only) */\n\tgetFollowUpMessages(): readonly string[] {\n\t\treturn this._followUpMessages;\n\t}\n\n\tget resourceLoader(): ResourceLoader {\n\t\treturn this._resourceLoader;\n\t}\n\n\t/**\n\t * Abort current operation and wait for agent to become idle.\n\t */\n\tasync abort(): Promise<void> {\n\t\tthis.abortRetry();\n\t\tthis.agent.abort();\n\t\tawait this.waitForIdle();\n\t}\n\n\tasync waitForIdle(): Promise<void> {\n\t\tif (this.isIdle) {\n\t\t\treturn;\n\t\t}\n\t\tawait this._getIdleWaitPromise();\n\t}\n\n\t// =========================================================================\n\t// Model Management\n\t// =========================================================================\n\n\tprivate async _emitModelSelect(\n\t\tnextModel: Model<any>,\n\t\tpreviousModel: Model<any> | undefined,\n\t\tsource: \"set\" | \"cycle\" | \"restore\",\n\t): Promise<void> {\n\t\tif (modelsAreEqual(previousModel, nextModel)) return;\n\t\tawait this._extensionRunner.emit({\n\t\t\ttype: \"model_select\",\n\t\t\tmodel: nextModel,\n\t\t\tpreviousModel,\n\t\t\tsource,\n\t\t});\n\t}\n\n\t/**\n\t * Set model directly.\n\t * Validates that auth is configured and saves to the session transcript.\n\t * Persists to global defaults only when options.persist is true.\n\t * @throws Error if no auth is configured for the model\n\t */\n\tasync setModel(model: Model<any>, options: ModelMutationOptions = {}): Promise<void> {\n\t\tif (!(await this._modelRuntime.checkAuth(model.provider))) {\n\t\t\tthrow new Error(`No API key for ${model.provider}/${model.id}`);\n\t\t}\n\n\t\tconst previousModel = this.model;\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(model);\n\t\tthis.agent.state.model = model;\n\t\tthis.sessionManager.appendModelChange(model.provider, model.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(model.provider, model.id);\n\t\t}\n\n\t\t// Apply thinking level for the new model.\n\t\t// Per-model thinking level overrides take priority over the global default.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(model, previousModel, \"set\");\n\t}\n\n\t/**\n\t * Cycle to next/previous model.\n\t * Uses scoped models (from --models flag) if available, otherwise all available models.\n\t * @param direction - \"forward\" (default) or \"backward\"\n\t * @returns The new model info, or undefined if only one model available\n\t */\n\tasync cycleModel(\n\t\tdirection: \"forward\" | \"backward\" = \"forward\",\n\t\toptions: ModelMutationOptions = {},\n\t): Promise<ModelCycleResult | undefined> {\n\t\tif (this._scopedModels.length > 0) {\n\t\t\treturn this._cycleScopedModel(direction, options);\n\t\t}\n\t\treturn this._cycleAvailableModel(direction, options);\n\t}\n\n\tprivate async _cycleScopedModel(\n\t\tdirection: \"forward\" | \"backward\",\n\t\toptions: ModelMutationOptions,\n\t): Promise<ModelCycleResult | undefined> {\n\t\tconst availableIds = new Set(\n\t\t\tthis._modelRuntime.getAvailableSnapshot().map((model) => `${model.provider}\\0${model.id}`),\n\t\t);\n\t\tconst scopedModels = this._scopedModels.filter((scoped) =>\n\t\t\tavailableIds.has(`${scoped.model.provider}\\0${scoped.model.id}`),\n\t\t);\n\t\tif (scopedModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = scopedModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst next = scopedModels[nextIndex];\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(next.model, next.thinkingLevel);\n\n\t\t// Apply model\n\t\tthis.agent.state.model = next.model;\n\t\tthis.sessionManager.appendModelChange(next.model.provider, next.model.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);\n\t\t}\n\n\t\t// Apply thinking level for the new model.\n\t\t// - Explicit scoped model thinking level overrides defaults\n\t\t// - Per-model thinking level overrides take priority over the global default\n\t\t// setThinkingLevel clamps to model capabilities.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(next.model, currentModel, \"cycle\");\n\n\t\treturn { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };\n\t}\n\n\tprivate async _cycleAvailableModel(\n\t\tdirection: \"forward\" | \"backward\",\n\t\toptions: ModelMutationOptions,\n\t): Promise<ModelCycleResult | undefined> {\n\t\tconst availableModels = this._modelRuntime.getAvailableSnapshot();\n\t\tif (availableModels.length <= 1) return undefined;\n\n\t\tconst currentModel = this.model;\n\t\tlet currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));\n\n\t\tif (currentIndex === -1) currentIndex = 0;\n\t\tconst len = availableModels.length;\n\t\tconst nextIndex = direction === \"forward\" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;\n\t\tconst nextModel = availableModels[nextIndex];\n\n\t\tconst thinkingLevel = this._getThinkingLevelForModelSwitch(nextModel);\n\t\tthis.agent.state.model = nextModel;\n\t\tthis.sessionManager.appendModelChange(nextModel.provider, nextModel.id);\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);\n\t\t}\n\n\t\t// Apply thinking level for the new model.\n\t\t// Model persistence does not implicitly rewrite the global thinking default.\n\t\tthis.setThinkingLevel(thinkingLevel);\n\n\t\tawait this._emitModelSelect(nextModel, currentModel, \"cycle\");\n\n\t\treturn { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };\n\t}\n\n\t// =========================================================================\n\t// Thinking Level Management\n\t// =========================================================================\n\n\t/**\n\t * Set thinking level.\n\t * Clamps to model capabilities based on available thinking levels.\n\t * Saves the clamped level to the session transcript only if the level actually changes.\n\t * Persists the requested level to global defaults only when options.persist is true.\n\t */\n\tsetThinkingLevel(level: ThinkingLevel, options: ModelMutationOptions = {}): void {\n\t\tconst availableLevels = this.getAvailableThinkingLevels();\n\t\tconst effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels);\n\n\t\t// Only persist if actually changing\n\t\tconst previousLevel = this.agent.state.thinkingLevel;\n\t\tconst isChanging = effectiveLevel !== previousLevel;\n\n\t\tthis.agent.state.thinkingLevel = effectiveLevel;\n\n\t\tif (options.persist) {\n\t\t\tthis.settingsManager.setDefaultThinkingLevel(level);\n\t\t}\n\n\t\tif (isChanging) {\n\t\t\tthis.sessionManager.appendThinkingLevelChange(effectiveLevel);\n\t\t\tthis._emit({ type: \"thinking_level_changed\", level: effectiveLevel });\n\t\t\tvoid this._extensionRunner.emit({\n\t\t\t\ttype: \"thinking_level_select\",\n\t\t\t\tlevel: effectiveLevel,\n\t\t\t\tpreviousLevel,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Cycle to next thinking level.\n\t * @returns New level, or undefined if model doesn't support thinking\n\t */\n\tcycleThinkingLevel(options: ModelMutationOptions = {}): ThinkingLevel | undefined {\n\t\tif (!this.supportsThinking()) return undefined;\n\n\t\tconst levels = this.getAvailableThinkingLevels();\n\t\tconst currentIndex = levels.indexOf(this.thinkingLevel);\n\t\tconst nextIndex = (currentIndex + 1) % levels.length;\n\t\tconst nextLevel = levels[nextIndex];\n\n\t\tthis.setThinkingLevel(nextLevel, options);\n\t\treturn nextLevel;\n\t}\n\n\t/**\n\t * Get available thinking levels for current model.\n\t * The provider will clamp to what the specific model supports internally.\n\t */\n\tgetAvailableThinkingLevels(): ThinkingLevel[] {\n\t\tif (!this.model) return [...THINKING_LEVEL_OPTIONS];\n\t\treturn getSupportedThinkingLevels(this.model) as ThinkingLevel[];\n\t}\n\n\t/**\n\t * Check if current model supports thinking/reasoning.\n\t */\n\tsupportsThinking(): boolean {\n\t\treturn !!this.model?.reasoning;\n\t}\n\n\tprivate _getThinkingLevelForModelSwitch(targetModel?: Model<any>, explicitLevel?: ThinkingLevel): ThinkingLevel {\n\t\tif (explicitLevel !== undefined) {\n\t\t\treturn explicitLevel;\n\t\t}\n\t\t// Per-model default takes priority when switching to a model that has one\n\t\tif (targetModel) {\n\t\t\tconst perModel = this.settingsManager.getModelThinkingLevel(targetModel.provider, targetModel.id);\n\t\t\tif (perModel !== undefined) {\n\t\t\t\treturn perModel;\n\t\t\t}\n\t\t}\n\t\treturn this.settingsManager.getDefaultThinkingLevel() ?? this.thinkingLevel ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\tprivate _clampThinkingLevel(level: ThinkingLevel, _availableLevels: ThinkingLevel[]): ThinkingLevel {\n\t\treturn this.model ? (clampThinkingLevel(this.model, level) as ThinkingLevel) : \"off\";\n\t}\n\n\t// =========================================================================\n\t// Queue Mode Management\n\t// =========================================================================\n\n\tprivate syncQueueModesFromSettings(): void {\n\t\tthis.agent.steeringMode = this.settingsManager.getSteeringMode();\n\t\tthis.agent.followUpMode = this.settingsManager.getFollowUpMode();\n\t}\n\n\t/**\n\t * Set steering message mode.\n\t * Saves to settings.\n\t */\n\tsetSteeringMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.agent.steeringMode = mode;\n\t\tthis.settingsManager.setSteeringMode(mode);\n\t}\n\n\t/**\n\t * Set follow-up message mode.\n\t * Saves to settings.\n\t */\n\tsetFollowUpMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.agent.followUpMode = mode;\n\t\tthis.settingsManager.setFollowUpMode(mode);\n\t}\n\n\t// =========================================================================\n\t// Compaction\n\t// =========================================================================\n\n\t/** Generate Pi's built-in compaction summary for manual and automatic compaction. */\n\tprivate async _runDefaultCompaction(\n\t\tpreparation: CompactionPreparation,\n\t\trequestModel: Model<any>,\n\t\tapiKey: string | undefined,\n\t\theaders: Record<string, string> | undefined,\n\t\tcustomInstructions: string | undefined,\n\t\tsignal: AbortSignal,\n\t\tenv: Record<string, string> | undefined,\n\t\treason: \"manual\" | \"threshold\" | \"overflow\",\n\t): Promise<CompactionResult> {\n\t\t// Ada: al resumen en prosa se le añade un REGISTRO DENSO de hechos\n\t\t// (rutas, comandos, errores) extraídos literalmente de los turnos. Este\n\t\t// es el ÚNICO punto por el que pasan las dos compactaciones de pi (la\n\t\t// manual de `/compact`, RPC y extensiones, y la automática por\n\t\t// umbral/desbordamiento), así que un solo hunk cubre la ruta entera.\n\t\t// Ver core/compactar/ para el porqué de sumar en vez de sustituir.\n\t\treturn anadirRegistroDenso(\n\t\t\tawait compact(\n\t\t\t\tpreparation,\n\t\t\t\trequestModel,\n\t\t\t\tapiKey,\n\t\t\t\theaders,\n\t\t\t\tcustomInstructions,\n\t\t\t\tsignal,\n\t\t\t\tthis.thinkingLevel,\n\t\t\t\tthis.agent.streamFunction,\n\t\t\t\tenv,\n\t\t\t\tthis.settingsManager.getRetrySettings(),\n\t\t\t\tthis._summarizationRetryCallbacks({ source: \"compaction\", reason }),\n\t\t\t\tundefined, // sessionId\n\t\t\t),\n\t\t\tpreparation,\n\t\t);\n\t}\n\n\t/**\n\t * Manually compact the session context.\n\t *\n\t * This is the manual entry point used by `/compact`, RPC, and extensions. It is\n\t * separate from automatic threshold/overflow compaction, which enters through\n\t * `_checkCompaction()` and `_runAutoCompaction()`. After preparation and the\n\t * `session_before_compact` hook, both paths call the lower-level `compact()`\n\t * function imported from `./compaction/index.ts`, unless the hook cancels or\n\t * supplies a custom result.\n\t *\n\t * Aborts the current agent operation first. Manual compaction never retries or\n\t * continues the interrupted agent turn.\n\t *\n\t * @param customInstructions Optional instructions for the compaction summary\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tawait this.abort();\n\t\tthis._compactionAbortController = new AbortController();\n\t\tthis._emit({ type: \"compaction_start\", reason: \"manual\" });\n\t\tlet fromExtension = false;\n\n\t\ttry {\n\t\t\tif (!this.model) {\n\t\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t\t}\n\n\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);\n\n\t\t\tconst pathEntries = this.sessionManager.getBranch();\n\t\t\tconst settings = this.settingsManager.getCompactionSettings();\n\n\t\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\t\tif (!preparation) {\n\t\t\t\t// Check why we can't compact\n\t\t\t\tconst lastEntry = pathEntries[pathEntries.length - 1];\n\t\t\t\tif (lastEntry?.type === \"compaction\") {\n\t\t\t\t\tthrow new Error(\"Already compacted\");\n\t\t\t\t}\n\t\t\t\tthrow new Error(\"Nothing to compact (session too small)\");\n\t\t\t}\n\n\t\t\tlet extensionCompaction: CompactionResult | undefined;\n\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t\t\tconst result = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_compact\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tbranchEntries: pathEntries,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treason: \"manual\",\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tsignal: this._compactionAbortController.signal,\n\t\t\t\t})) as SessionBeforeCompactResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t\t\t}\n\n\t\t\t\tif (result?.compaction) {\n\t\t\t\t\textensionCompaction = result.compaction;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet summary: string;\n\t\t\tlet firstKeptEntryId: string;\n\t\t\tlet tokensBefore: number;\n\t\t\tlet usage: Usage | undefined;\n\t\t\tlet details: unknown;\n\n\t\t\tif (extensionCompaction) {\n\t\t\t\t// Extension provided compaction content\n\t\t\t\tsummary = extensionCompaction.summary;\n\t\t\t\tfirstKeptEntryId = extensionCompaction.firstKeptEntryId;\n\t\t\t\ttokensBefore = extensionCompaction.tokensBefore;\n\t\t\t\tusage = extensionCompaction.usage;\n\t\t\t\tdetails = extensionCompaction.details;\n\t\t\t} else {\n\t\t\t\t// Shared default summary generator, also used by automatic compaction.\n\t\t\t\tconst result = await this._runDefaultCompaction(\n\t\t\t\t\tpreparation,\n\t\t\t\t\trequestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\tthis._compactionAbortController.signal,\n\t\t\t\t\tenv,\n\t\t\t\t\t\"manual\",\n\t\t\t\t);\n\t\t\t\tsummary = result.summary;\n\t\t\t\tfirstKeptEntryId = result.firstKeptEntryId;\n\t\t\t\ttokensBefore = result.tokensBefore;\n\t\t\t\tusage = result.usage;\n\t\t\t\tdetails = result.details;\n\t\t\t}\n\n\t\t\tif (this._compactionAbortController.signal.aborted) {\n\t\t\t\tthrow new Error(\"Compaction cancelled\");\n\t\t\t}\n\n\t\t\tthis.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);\n\t\t\tconst newEntries = this.sessionManager.getEntries();\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\t\t\tconst estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);\n\n\t\t\t// Get the saved compaction entry for the extension event\n\t\t\tconst savedCompactionEntry = newEntries.find((e) => e.type === \"compaction\" && e.summary === summary) as\n\t\t\t\t| CompactionEntry\n\t\t\t\t| undefined;\n\n\t\t\tif (this._extensionRunner && savedCompactionEntry) {\n\t\t\t\tawait this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_compact\",\n\t\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\treason: \"manual\",\n\t\t\t\t\twillRetry: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst compactionResult: CompactionResult = {\n\t\t\t\tsummary,\n\t\t\t\tfirstKeptEntryId,\n\t\t\t\ttokensBefore,\n\t\t\t\testimatedTokensAfter,\n\t\t\t\tusage,\n\t\t\t\tdetails,\n\t\t\t};\n\t\t\t// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: compactionResult,\n\t\t\t\taborted: false,\n\t\t\t\twillRetry: false,\n\t\t\t});\n\t\t\treturn compactionResult;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst aborted = message === \"Compaction cancelled\" || (error instanceof Error && error.name === \"AbortError\");\n\t\t\tconst errorMessage = aborted ? undefined : `Compaction failed: ${message}`;\n\t\t\tthis._compactionAbortController = undefined;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"compaction_end\",\n\t\t\t\treason: \"manual\",\n\t\t\t\tresult: undefined,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\terrorMessage,\n\t\t\t});\n\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\treason: \"manual\",\n\t\t\t\terrorMessage,\n\t\t\t\taborted,\n\t\t\t\twillRetry: false,\n\t\t\t\tfromExtension,\n\t\t\t});\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis._compactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Cancel in-progress compaction (manual or auto).\n\t */\n\tabortCompaction(): void {\n\t\tthis._compactionAbortController?.abort();\n\t\tthis._autoCompactionAbortController?.abort();\n\t}\n\n\t/**\n\t * Cancel in-progress branch summarization.\n\t */\n\tabortBranchSummary(): void {\n\t\tthis._branchSummaryAbortController?.abort();\n\t}\n\n\t/**\n\t * Dispatch automatic compaction after `agent_end` or before prompt submission.\n\t * Manual compaction does not call this method; it enters through `compact()`.\n\t *\n\t * Automatic cases:\n\t * 1. Overflow with retry: a context-overflow error or recoverable length stop;\n\t * remove the failed assistant message, compact, and retry the turn once.\n\t * 2. Overflow without retry: a successful response exceeded the configured\n\t * context window; compact but preserve the completed response.\n\t * 3. Threshold without retry: valid or estimated context usage crossed the\n\t * configured threshold; compact without retrying the completed response.\n\t *\n\t * Each case calls `_runAutoCompaction()`. After preparation and the\n\t * `session_before_compact` hook, that method calls the lower-level `compact()`\n\t * function imported from `./compaction/index.ts`, unless the hook cancels or\n\t * supplies a custom result.\n\t *\n\t * @param assistantMessage The assistant message to check\n\t * @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true\n\t * @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages\n\t */\n\tprivate async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tif (!settings.enabled) return false;\n\n\t\t// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false\n\t\tif (skipAbortedCheck && assistantMessage.stopReason === \"aborted\") return false;\n\n\t\tconst contextWindow = this.model?.contextWindow ?? 0;\n\n\t\t// Skip overflow check if the message came from a different model.\n\t\t// This handles the case where user switched from a smaller-context model (e.g. opus)\n\t\t// to a larger-context model (e.g. codex) - the overflow error from the old model\n\t\t// shouldn't trigger compaction for the new model.\n\t\tconst sameModel =\n\t\t\tthis.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;\n\n\t\t// Skip compaction checks if this assistant message is older than the latest\n\t\t// compaction boundary. This prevents a stale pre-compaction usage/error\n\t\t// from retriggering compaction on the first prompt after compaction.\n\t\tconst compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());\n\t\tconst assistantIsFromBeforeCompaction =\n\t\t\tcompactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();\n\t\tif (assistantIsFromBeforeCompaction) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Automatic cases 1 and 2: context overflow.\n\t\t// A length stop is recoverable when output ended below the model's original desired limit,\n\t\t// independent of the configured context size or any context-clamped provider request limit.\n\t\tconst contextOverflow = sameModel && isContextOverflow(assistantMessage, contextWindow);\n\t\tconst recoverableLength = sameModel && isRecoverableLength(assistantMessage, this.model?.maxTokens ?? 0);\n\t\tif (contextOverflow || recoverableLength) {\n\t\t\tconst willRetry = assistantMessage.stopReason !== \"stop\";\n\n\t\t\t// Case 2: the response completed successfully. Compact, but do not retry because\n\t\t\t// agent.continue() cannot continue from a completed assistant response.\n\t\t\tif (!willRetry) {\n\t\t\t\treturn await this._runAutoCompaction(\"overflow\", false);\n\t\t\t}\n\n\t\t\tif (this._overflowRecoveryAttempted) {\n\t\t\t\tconst errorMessage = contextOverflow\n\t\t\t\t\t? \"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.\"\n\t\t\t\t\t: \"Truncated response recovery failed after one compact-and-retry attempt.\";\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\terrorMessage,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension: false,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Case 1: remove the failed or truncated message from agent state, compact, and\n\t\t\t// retry once. The message remains in session history but is excluded from retry context.\n\t\t\tthis._overflowRecoveryAttempted = true;\n\t\t\tconst messages = this.agent.state.messages;\n\t\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t}\n\t\t\treturn await this._runAutoCompaction(\"overflow\", willRetry);\n\t\t}\n\n\t\t// Case 3: threshold compaction without retry.\n\t\t// For error messages or all-zero usage messages, estimate from the last valid response.\n\t\t// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage\n\t\t// responses can still compact and do not reset context accounting.\n\t\tlet contextTokens: number;\n\t\tconst directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;\n\t\tif (assistantMessage.stopReason === \"error\" || directContextTokens === 0) {\n\t\t\tconst messages = this.agent.state.messages;\n\t\t\tconst estimate = estimateContextTokens(messages);\n\t\t\t// Without provider usage, estimate.tokens is the pure message-size estimate.\n\t\t\t// Only usage-backed estimates need the stale pre-compaction check.\n\t\t\tif (estimate.lastUsageIndex !== null) {\n\t\t\t\t// Verify the usage source is post-compaction. Kept pre-compaction messages\n\t\t\t\t// have stale usage reflecting the old (larger) context and would falsely\n\t\t\t\t// trigger compaction right after one just finished.\n\t\t\t\tconst usageMsg = messages[estimate.lastUsageIndex];\n\t\t\t\tif (\n\t\t\t\t\tcompactionEntry &&\n\t\t\t\t\tusageMsg.role === \"assistant\" &&\n\t\t\t\t\t(usageMsg as AssistantMessage).timestamp <= new Date(compactionEntry.timestamp).getTime()\n\t\t\t\t) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontextTokens = estimate.tokens;\n\t\t} else {\n\t\t\tcontextTokens = directContextTokens;\n\t\t}\n\t\tif (shouldCompact(contextTokens, contextWindow, settings)) {\n\t\t\treturn await this._runAutoCompaction(\"threshold\", false);\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Execute threshold or overflow compaction. Manual compaction uses\n\t * `AgentSession.compact()` instead. Both paths call the lower-level `compact()`\n\t * function imported from `./compaction/index.ts` after preparation and extension\n\t * interception.\n\t *\n\t * @param reason Automatic trigger selected by `_checkCompaction()`\n\t * @param willRetry Whether to continue the interrupted turn after overflow compaction\n\t * @returns Whether the post-run loop should call `agent.continue()`\n\t */\n\tprivate async _runAutoCompaction(reason: \"overflow\" | \"threshold\", willRetry: boolean): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getCompactionSettings();\n\t\tlet started = false;\n\t\tlet fromExtension = false;\n\n\t\ttry {\n\t\t\tif (!this.model) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);\n\n\t\t\tconst pathEntries = this.sessionManager.getBranch();\n\n\t\t\tconst preparation = prepareCompaction(pathEntries, settings);\n\t\t\tif (!preparation) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis._emit({ type: \"compaction_start\", reason });\n\t\t\tthis._autoCompactionAbortController = new AbortController();\n\t\t\tstarted = true;\n\n\t\t\tlet extensionCompaction: CompactionResult | undefined;\n\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_compact\")) {\n\t\t\t\tconst extensionResult = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_compact\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tbranchEntries: pathEntries,\n\t\t\t\t\tcustomInstructions: undefined,\n\t\t\t\t\treason,\n\t\t\t\t\twillRetry,\n\t\t\t\t\tsignal: this._autoCompactionAbortController.signal,\n\t\t\t\t})) as SessionBeforeCompactResult | undefined;\n\n\t\t\t\tif (extensionResult?.cancel) {\n\t\t\t\t\tthis._emit({\n\t\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\t\treason,\n\t\t\t\t\t\tresult: undefined,\n\t\t\t\t\t\taborted: true,\n\t\t\t\t\t\twillRetry: false,\n\t\t\t\t\t});\n\t\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\t\treason,\n\t\t\t\t\t\taborted: true,\n\t\t\t\t\t\twillRetry: false,\n\t\t\t\t\t\tfromExtension: false,\n\t\t\t\t\t});\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (extensionResult?.compaction) {\n\t\t\t\t\textensionCompaction = extensionResult.compaction;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet summary: string;\n\t\t\tlet firstKeptEntryId: string;\n\t\t\tlet tokensBefore: number;\n\t\t\tlet usage: Usage | undefined;\n\t\t\tlet details: unknown;\n\n\t\t\tif (extensionCompaction) {\n\t\t\t\t// Extension provided compaction content\n\t\t\t\tsummary = extensionCompaction.summary;\n\t\t\t\tfirstKeptEntryId = extensionCompaction.firstKeptEntryId;\n\t\t\t\ttokensBefore = extensionCompaction.tokensBefore;\n\t\t\t\tusage = extensionCompaction.usage;\n\t\t\t\tdetails = extensionCompaction.details;\n\t\t\t} else {\n\t\t\t\t// Shared default summary generator, also used by manual compaction.\n\t\t\t\tconst compactResult = await this._runDefaultCompaction(\n\t\t\t\t\tpreparation,\n\t\t\t\t\trequestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tundefined,\n\t\t\t\t\tthis._autoCompactionAbortController.signal,\n\t\t\t\t\tenv,\n\t\t\t\t\treason,\n\t\t\t\t);\n\t\t\t\tsummary = compactResult.summary;\n\t\t\t\tfirstKeptEntryId = compactResult.firstKeptEntryId;\n\t\t\t\ttokensBefore = compactResult.tokensBefore;\n\t\t\t\tusage = compactResult.usage;\n\t\t\t\tdetails = compactResult.details;\n\t\t\t}\n\n\t\t\tif (this._autoCompactionAbortController.signal.aborted) {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason,\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: true,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason,\n\t\t\t\t\taborted: true,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension,\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);\n\t\t\tconst newEntries = this.sessionManager.getEntries();\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\t\t\tconst estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);\n\n\t\t\t// Get the saved compaction entry for the extension event\n\t\t\tconst savedCompactionEntry = newEntries.find((e) => e.type === \"compaction\" && e.summary === summary) as\n\t\t\t\t| CompactionEntry\n\t\t\t\t| undefined;\n\n\t\t\tif (this._extensionRunner && savedCompactionEntry) {\n\t\t\t\tawait this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_compact\",\n\t\t\t\t\tcompactionEntry: savedCompactionEntry,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\treason,\n\t\t\t\t\twillRetry,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst result: CompactionResult = {\n\t\t\t\tsummary,\n\t\t\t\tfirstKeptEntryId,\n\t\t\t\ttokensBefore,\n\t\t\t\testimatedTokensAfter,\n\t\t\t\tusage,\n\t\t\t\tdetails,\n\t\t\t};\n\t\t\tthis._emit({ type: \"compaction_end\", reason, result, aborted: false, willRetry });\n\n\t\t\tif (willRetry) {\n\t\t\t\tconst messages = this.agent.state.messages;\n\t\t\t\tconst lastMsg = messages[messages.length - 1];\n\t\t\t\t// The overflow response was persisted on message_end before _checkCompaction() removed it\n\t\t\t\t// from agent state. Rebuilding state from the new compaction can restore that kept entry,\n\t\t\t\t// leaving an assistant as the final message. agent.continue() rejects that state, so remove\n\t\t\t\t// the retriable error or truncated-length response again before continuing the interrupted turn.\n\t\t\t\tif (lastMsg?.role === \"assistant\" && (lastMsg.stopReason === \"error\" || lastMsg.stopReason === \"length\")) {\n\t\t\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Auto-compaction can complete while follow-up/steering/custom messages are waiting.\n\t\t\t// Continue once so queued messages are delivered.\n\t\t\treturn this.agent.hasQueuedMessages();\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"compaction failed\";\n\t\t\tif (started) {\n\t\t\t\tconst formattedErrorMessage =\n\t\t\t\t\treason === \"overflow\"\n\t\t\t\t\t\t? `Context overflow recovery failed: ${errorMessage}`\n\t\t\t\t\t\t: `Auto-compaction failed: ${errorMessage}`;\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"compaction_end\",\n\t\t\t\t\treason,\n\t\t\t\t\tresult: undefined,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\terrorMessage: formattedErrorMessage,\n\t\t\t\t});\n\t\t\t\tawait this._emitSessionCompactFailed({\n\t\t\t\t\treason,\n\t\t\t\t\terrorMessage: formattedErrorMessage,\n\t\t\t\t\taborted: false,\n\t\t\t\t\twillRetry: false,\n\t\t\t\t\tfromExtension,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._autoCompactionAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Toggle auto-compaction setting.\n\t */\n\tsetAutoCompactionEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setCompactionEnabled(enabled);\n\t}\n\n\t/** Whether auto-compaction is enabled */\n\tget autoCompactionEnabled(): boolean {\n\t\treturn this.settingsManager.getCompactionEnabled();\n\t}\n\n\tasync bindExtensions(bindings: ExtensionBindings): Promise<void> {\n\t\tif (bindings.uiContext !== undefined) {\n\t\t\tthis._extensionUIContext = bindings.uiContext;\n\t\t}\n\t\tif (bindings.mode !== undefined) {\n\t\t\tthis._extensionMode = bindings.mode;\n\t\t}\n\t\tif (bindings.commandContextActions !== undefined) {\n\t\t\tthis._extensionCommandContextActions = bindings.commandContextActions;\n\t\t}\n\t\tif (bindings.abortHandler !== undefined) {\n\t\t\tthis._extensionAbortHandler = bindings.abortHandler;\n\t\t}\n\t\tif (bindings.shutdownHandler !== undefined) {\n\t\t\tthis._extensionShutdownHandler = bindings.shutdownHandler;\n\t\t}\n\t\tif (bindings.onError !== undefined) {\n\t\t\tthis._extensionErrorListener = bindings.onError;\n\t\t}\n\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\t\tawait this._extensionRunner.emit(this._sessionStartEvent);\n\t\tawait this.extendResourcesFromExtensions(this._sessionStartEvent.reason === \"reload\" ? \"reload\" : \"startup\");\n\t}\n\n\tprivate async extendResourcesFromExtensions(reason: \"startup\" | \"reload\"): Promise<void> {\n\t\tif (!this._extensionRunner.hasHandlers(\"resources_discover\")) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(\n\t\t\tthis._cwd,\n\t\t\treason,\n\t\t);\n\n\t\tif (skillPaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst extensionPaths: ResourceExtensionPaths = {\n\t\t\tskillPaths: this.buildExtensionResourcePaths(skillPaths),\n\t\t\tpromptPaths: this.buildExtensionResourcePaths(promptPaths),\n\t\t\tthemePaths: this.buildExtensionResourcePaths(themePaths),\n\t\t};\n\n\t\tthis._resourceLoader.extendResources(extensionPaths);\n\t\tthis._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());\n\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t}\n\n\tprivate buildExtensionResourcePaths(entries: Array<{ path: string; extensionPath: string }>): Array<{\n\t\tpath: string;\n\t\tmetadata: { source: string; scope: \"temporary\"; origin: \"top-level\"; baseDir?: string };\n\t}> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst source = this.getExtensionSourceLabel(entry.extensionPath);\n\t\t\tconst baseDir = entry.extensionPath.startsWith(\"<\") ? undefined : dirname(entry.extensionPath);\n\t\t\treturn {\n\t\t\t\tpath: entry.path,\n\t\t\t\tmetadata: {\n\t\t\t\t\tsource,\n\t\t\t\t\tscope: \"temporary\",\n\t\t\t\t\torigin: \"top-level\",\n\t\t\t\t\tbaseDir,\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate getExtensionSourceLabel(extensionPath: string): string {\n\t\tif (extensionPath.startsWith(\"<\")) {\n\t\t\treturn `extension:${extensionPath.replace(/[<>]/g, \"\")}`;\n\t\t}\n\t\tconst base = basename(extensionPath);\n\t\tconst name = base.replace(/\\.(ts|js)$/, \"\");\n\t\treturn `extension:${name}`;\n\t}\n\n\tprivate _applyExtensionBindings(runner: ExtensionRunner): void {\n\t\trunner.setUIContext(this._extensionUIContext, this._extensionMode);\n\t\trunner.bindCommandContext(this._extensionCommandContextActions);\n\n\t\tthis._extensionErrorUnsubscriber?.();\n\t\tthis._extensionErrorUnsubscriber = this._extensionErrorListener\n\t\t\t? runner.onError(this._extensionErrorListener)\n\t\t\t: undefined;\n\t}\n\n\tprivate _refreshCurrentModelFromRegistry(): void {\n\t\tconst currentModel = this.model;\n\t\tif (!currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst refreshedModel = this._modelRuntime.getModel(currentModel.provider, currentModel.id);\n\t\tif (!refreshedModel || refreshedModel === currentModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.agent.state.model = refreshedModel;\n\t}\n\n\tprivate _bindExtensionCore(runner: ExtensionRunner): void {\n\t\tconst getCommands = (): SlashCommandInfo[] => {\n\t\t\tconst extensionCommands: SlashCommandInfo[] = runner.getRegisteredCommands().map((command) => ({\n\t\t\t\tname: command.invocationName,\n\t\t\t\tdescription: command.description,\n\t\t\t\tsource: \"extension\",\n\t\t\t\tsourceInfo: command.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst templates: SlashCommandInfo[] = this.promptTemplates.map((template) => ({\n\t\t\t\tname: template.name,\n\t\t\t\tdescription: template.description,\n\t\t\t\tsource: \"prompt\",\n\t\t\t\tsourceInfo: template.sourceInfo,\n\t\t\t}));\n\n\t\t\tconst skills: SlashCommandInfo[] = this._resourceLoader.getSkills().skills.map((skill) => ({\n\t\t\t\tname: `skill:${skill.name}`,\n\t\t\t\tdescription: skill.description,\n\t\t\t\tsource: \"skill\",\n\t\t\t\tsourceInfo: skill.sourceInfo,\n\t\t\t}));\n\n\t\t\treturn [...extensionCommands, ...templates, ...skills];\n\t\t};\n\n\t\trunner.bindCore(\n\t\t\t{\n\t\t\t\tsendMessage: (message, options) => {\n\t\t\t\t\tthis.sendCustomMessage(message, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tsendUserMessage: (content, options) => {\n\t\t\t\t\tthis.sendUserMessage(content, options).catch((err) => {\n\t\t\t\t\t\trunner.emitError({\n\t\t\t\t\t\t\textensionPath: \"<runtime>\",\n\t\t\t\t\t\t\tevent: \"send_user_message\",\n\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tappendEntry: (customType, data) => {\n\t\t\t\t\tconst entryId = this.sessionManager.appendCustomEntry(customType, data);\n\t\t\t\t\tconst entry = this.sessionManager.getEntry(entryId);\n\t\t\t\t\tif (entry) {\n\t\t\t\t\t\tthis._emit({ type: \"entry_appended\", entry });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tsetSessionName: (name) => {\n\t\t\t\t\tthis.setSessionName(name);\n\t\t\t\t},\n\t\t\t\tgetSessionName: () => {\n\t\t\t\t\treturn this.sessionManager.getSessionName();\n\t\t\t\t},\n\t\t\t\tsetLabel: (entryId, label) => {\n\t\t\t\t\tthis.sessionManager.appendLabelChange(entryId, label);\n\t\t\t\t},\n\t\t\t\tgetActiveTools: () => this.getActiveToolNames(),\n\t\t\t\tgetAllTools: () => this.getAllTools(),\n\t\t\t\tsetActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),\n\t\t\t\trefreshTools: () => this._refreshToolRegistry(),\n\t\t\t\tgetCommands,\n\t\t\t\tsetModel: async (model) => {\n\t\t\t\t\tif (!this._modelRuntime.hasConfiguredAuth(model.provider)) return false;\n\t\t\t\t\tawait this.setModel(model);\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tgetThinkingLevel: () => this.thinkingLevel,\n\t\t\t\tsetThinkingLevel: (level) => this.setThinkingLevel(level),\n\t\t\t},\n\t\t\t{\n\t\t\t\tgetModel: () => this.model,\n\t\t\t\tgetScopedModels: () => this._scopedModels,\n\t\t\t\tisIdle: () => this.isIdle,\n\t\t\t\tisProjectTrusted: () => this.settingsManager.isProjectTrusted(),\n\t\t\t\tgetSignal: () => this.agent.signal,\n\t\t\t\tabort: () => {\n\t\t\t\t\tif (this._extensionAbortHandler) {\n\t\t\t\t\t\tthis._extensionAbortHandler();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid this.abort();\n\t\t\t\t},\n\t\t\t\thasPendingMessages: () => this.pendingMessageCount > 0,\n\t\t\t\tshutdown: () => {\n\t\t\t\t\tthis._extensionShutdownHandler?.();\n\t\t\t\t},\n\t\t\t\tgetContextUsage: () => this.getContextUsage(),\n\t\t\t\tcompact: (options) => {\n\t\t\t\t\tvoid (async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst result = await this.compact(options?.customInstructions);\n\t\t\t\t\t\t\toptions?.onComplete?.(result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst err = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\t\t\toptions?.onError?.(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t\tgetSystemPrompt: () => this.systemPrompt,\n\t\t\t\tgetSystemPromptOptions: () => this._baseSystemPromptOptions,\n\t\t\t},\n\t\t\t{\n\t\t\t\tregisterProvider: (name, config) => {\n\t\t\t\t\tthis._modelRuntime.registerProvider(name, config);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tregisterNativeProvider: (provider) => {\n\t\t\t\t\tthis._modelRuntime.registerNativeProvider(provider);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t\tunregisterProvider: (name) => {\n\t\t\t\t\tthis._modelRuntime.unregisterProvider(name);\n\t\t\t\t\tthis._refreshCurrentModelFromRegistry();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate _refreshToolRegistry(options?: { activeToolNames?: string[]; includeAllExtensionTools?: boolean }): void {\n\t\tconst previousRegistryNames = new Set(this._toolRegistry.keys());\n\t\tconst previousActiveToolNames = this.getActiveToolNames();\n\t\tconst allowedToolNames = this._allowedToolNames;\n\t\tconst excludedToolNames = this._excludedToolNames;\n\t\tconst isAllowedTool = (name: string): boolean =>\n\t\t\t(!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name);\n\n\t\tconst registeredTools = this._extensionRunner.getAllRegisteredTools();\n\t\tconst allCustomTools = [\n\t\t\t...registeredTools,\n\t\t\t...this._customTools.map((definition) => ({\n\t\t\t\tdefinition,\n\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: \"sdk\" }),\n\t\t\t})),\n\t\t].filter((tool) => isAllowedTool(tool.definition.name));\n\t\tconst definitionRegistry = new Map<string, ToolDefinitionEntry>(\n\t\t\tArray.from(this._baseToolDefinitions.entries())\n\t\t\t\t.filter(([name]) => isAllowedTool(name))\n\t\t\t\t.map(([name, definition]) => [\n\t\t\t\t\tname,\n\t\t\t\t\t{\n\t\t\t\t\t\tdefinition,\n\t\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: \"builtin\" }),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t);\n\t\tfor (const tool of allCustomTools) {\n\t\t\tdefinitionRegistry.set(tool.definition.name, {\n\t\t\t\tdefinition: tool.definition,\n\t\t\t\tsourceInfo: tool.sourceInfo,\n\t\t\t});\n\t\t}\n\t\tthis._toolDefinitions = definitionRegistry;\n\t\tthis._toolPromptSnippets = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst snippet = this._normalizePromptSnippet(definition.promptSnippet);\n\t\t\t\t\treturn snippet ? ([definition.name, snippet] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string] => entry !== undefined),\n\t\t);\n\t\tthis._toolPromptGuidelines = new Map(\n\t\t\tArray.from(definitionRegistry.values())\n\t\t\t\t.map(({ definition }) => {\n\t\t\t\t\tconst guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);\n\t\t\t\t\treturn guidelines.length > 0 ? ([definition.name, guidelines] as const) : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((entry): entry is readonly [string, string[]] => entry !== undefined),\n\t\t);\n\t\tconst runner = this._extensionRunner;\n\t\tconst wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner);\n\t\tconst wrappedBuiltInTools = wrapRegisteredTools(\n\t\t\tArray.from(this._baseToolDefinitions.values())\n\t\t\t\t.filter((definition) => isAllowedTool(definition.name))\n\t\t\t\t.map((definition) => ({\n\t\t\t\t\tdefinition,\n\t\t\t\t\tsourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: \"builtin\" }),\n\t\t\t\t})),\n\t\t\trunner,\n\t\t);\n\n\t\tconst toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));\n\t\tfor (const tool of wrappedExtensionTools as AgentTool[]) {\n\t\t\ttoolRegistry.set(tool.name, tool);\n\t\t}\n\t\tthis._toolRegistry = toolRegistry;\n\n\t\tconst nextActiveToolNames = (\n\t\t\toptions?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]\n\t\t).filter((name) => isAllowedTool(name));\n\n\t\tif (allowedToolNames) {\n\t\t\tfor (const toolName of this._toolRegistry.keys()) {\n\t\t\t\tif (allowedToolNames.has(toolName)) {\n\t\t\t\t\tnextActiveToolNames.push(toolName);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (options?.includeAllExtensionTools) {\n\t\t\tfor (const tool of wrappedExtensionTools) {\n\t\t\t\tnextActiveToolNames.push(tool.name);\n\t\t\t}\n\t\t} else if (!options?.activeToolNames) {\n\t\t\tfor (const toolName of this._toolRegistry.keys()) {\n\t\t\t\tif (!previousRegistryNames.has(toolName)) {\n\t\t\t\t\tnextActiveToolNames.push(toolName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Lo que el PROMPT promete tiene que estar ACTIVO, no solo registrado.\n\t\t//\n\t\t// `sdk.ts` pasa una lista explícita de cuatro tools (read, bash, edit,\n\t\t// write), y eso apaga la rama de arriba que activaría lo recién\n\t\t// registrado. Consecuencia medida: 29 tools construidas e invisibles para\n\t\t// el modelo, mientras el prompt le ordenaba usar cinco de ellas —\n\t\t// \"usa la tool goal_new\", \"Use memory_search when…\", \"→ browser_*\". El\n\t\t// agente las pedía, recibía un error opaco, y desde fuera parecía que \"no\n\t\t// usa las funciones\".\n\t\t//\n\t\t// Se activa POR PREFIJO y no con una lista de nombres a propósito: una\n\t\t// lista se desincroniza en cuanto alguien añade una tool a una familia, y\n\t\t// esa desincronización silenciosa es justo el defecto que esto arregla.\n\t\t// `_toolRegistry` ya viene filtrado por `isAllowedTool`, así que quien\n\t\t// restrinja tools con `options.tools` sigue mandando.\n\t\t//\n\t\t// `find`, `grep` y `ls` se quedan FUERA deliberadamente: el prompt dice\n\t\t// \"usa bash para operaciones de ficheros (ls, grep, find)\", así que sus\n\t\t// tools serían un segundo camino para lo mismo — el problema de elección\n\t\t// que ya nos costó trabajo con computer_* y browser_*.\n\t\t// PERO solo cuando nadie ha elegido. Esto es un DEFECTO, no un override:\n\t\t// si el usuario configuró `defaultTools`, o el llamante pasó `tools` o\n\t\t// `noTools`, esa decisión manda y aquí no se toca nada. La primera versión\n\t\t// de este bloque no lo distinguía y rompía cuatro contratos ya probados,\n\t\t// entre ellos \"defaultTools se respeta tal cual\".\n\t\t//\n\t\t// La señal de \"nadie ha elegido\" es que la lista entrante sea exactamente\n\t\t// el núcleo que `sdk.ts` usa como valor por defecto. Cualquier otra cosa\n\t\t// —una lista propia, `noTools`, o una selección ya expandida— significa\n\t\t// que alguien decidió, y se respeta.\n\t\t//\n\t\t// Solo hace falta en la PRIMERA construcción: en refrescos posteriores, la\n\t\t// rama de arriba ya activa lo que se acabe de registrar, así que encender\n\t\t// una capacidad en caliente sigue funcionando.\n\t\tconst nucleoPorDefecto = [\"read\", \"bash\", \"edit\", \"write\"].filter(isAllowedTool);\n\t\tconst entrante = options?.activeToolNames ?? previousActiveToolNames;\n\t\tconst nadieHaElegido =\n\t\t\tthis._allowedToolNames === undefined &&\n\t\t\tthis.settingsManager.getDefaultTools() === undefined &&\n\t\t\tentrante.length === nucleoPorDefecto.length &&\n\t\t\tnucleoPorDefecto.every((nombre) => entrante.includes(nombre));\n\n\t\tconst cfg = this.settingsManager.getEngineConfig();\n\t\tconst capacidades: Array<{ prefijo: string; disponible: boolean }> = [\n\t\t\t// Sin condición: el prompt las ordena siempre.\n\t\t\t{ prefijo: \"memory_\", disponible: true },\n\t\t\t{ prefijo: \"session_search\", disponible: true },\n\t\t\t{ prefijo: \"web_\", disponible: true },\n\t\t\t{ prefijo: \"ast_\", disponible: true },\n\t\t\t{ prefijo: \"update_project_memory\", disponible: true },\n\t\t\t{ prefijo: \"scratchpad\", disponible: true },\n\t\t\t{ prefijo: \"todo\", disponible: true },\n\t\t\t// Preguntar en vez de adivinar: sin esto el modelo solo puede suponer.\n\t\t\t{ prefijo: \"ask_user_question\", disponible: true },\n\t\t\t// Con condición: cada una tiene su interruptor y su panel.\n\t\t\t{ prefijo: \"browser_\", disponible: cfg.computerUse.browser },\n\t\t\t{ prefijo: \"computer_\", disponible: cfg.computerUse.enabled },\n\t\t\t{ prefijo: \"goal_\", disponible: cfg.goalEngine.enabled },\n\t\t\t{ prefijo: \"workflow\", disponible: cfg.workflows.enabled },\n\t\t\t{ prefijo: \"graph_\", disponible: cfg.graph.enabled },\n\t\t\t{ prefijo: \"bash_\", disponible: true },\n\t\t];\n\t\tfor (const { prefijo, disponible } of nadieHaElegido ? capacidades : []) {\n\t\t\tif (!disponible) continue;\n\t\t\tfor (const nombre of this._toolRegistry.keys()) {\n\t\t\t\tif (nombre.startsWith(prefijo)) nextActiveToolNames.push(nombre);\n\t\t\t}\n\t\t}\n\n\t\tthis.setActiveToolsByName([...new Set(nextActiveToolNames)]);\n\t}\n\n\tprivate _buildRuntime(options: {\n\t\tactiveToolNames?: string[];\n\t\tflagValues?: Map<string, boolean | string>;\n\t\tincludeAllExtensionTools?: boolean;\n\t}): void {\n\t\tconst autoResizeImages = this.settingsManager.getImageAutoResize();\n\t\tconst shellCommandPrefix = this.settingsManager.getShellCommandPrefix();\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst baseToolDefinitions = this._baseToolsOverride\n\t\t\t? Object.fromEntries(\n\t\t\t\t\tObject.entries(this._baseToolsOverride).map(([name, tool]) => [\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tcreateToolDefinitionFromAgentTool(tool),\n\t\t\t\t\t]),\n\t\t\t\t)\n\t\t\t: createAllToolDefinitions(\n\t\t\t\t\tthis._cwd,\n\t\t\t\t\t{\n\t\t\t\t\t\tread: { autoResizeImages },\n\t\t\t\t\t\tbash: { commandPrefix: shellCommandPrefix, shellPath },\n\t\t\t\t\t},\n\t\t\t\t\tthis._resourceLoader.getAgentDir(),\n\t\t\t\t);\n\n\t\tthis._baseToolDefinitions = new Map(\n\t\t\tObject.entries(baseToolDefinitions).map(([name, tool]) => [name, tool as ToolDefinition]),\n\t\t);\n\n\t\t// The shared project memory tool is only exposed when enabled in settings.\n\t\tif (!this.settingsManager.getProjectMemoryEnabled()) {\n\t\t\tthis._baseToolDefinitions.delete(\"update_project_memory\");\n\t\t}\n\n\t\t// Ada Memory Engine tools (memory_add/replace/remove/search,\n\t\t// session_search, scratchpad, memory_status). Exposed when enabled.\n\t\t// Solo en la sesión principal: un subagente no debe poder mutar la\n\t\t// memoria durable del usuario (anti-recursión, igual que goal_* y\n\t\t// workflow abajo). Hasta ahora la propiedad se sostenía solo por la\n\t\t// allowlist de tools del call site, no por una guardia aquí.\n\t\tconst memoryEngine = this._resourceLoader.getMemoryEngine();\n\t\tif (memoryEngine && !this._isSubagentSession && this.settingsManager.getProjectMemoryEnabled()) {\n\t\t\tconst engineTools = createMemoryEngineTools(memoryEngine);\n\t\t\tfor (const [name, tool] of Object.entries(engineTools)) {\n\t\t\t\tthis._baseToolDefinitions.set(name, tool as unknown as ToolDefinition);\n\t\t\t}\n\t\t}\n\n\t\t// URLs internas de `read`: memory://, vault://, rule://, audit:// — un\n\t\t// solo `read` en vez de veinte tools (ver core/urls-internas/lector.ts).\n\t\t// `read.ts` es de upstream y el presupuesto de sync está al tope, así que\n\t\t// esto envuelve la definición ya construida en vez de tocar ese fichero.\n\t\t//\n\t\t// memory:// nunca se pasa a un subagente: es la misma anti-recursión que\n\t\t// bloquea memory_* dos bloques más arriba. Sin esto, `read` (que SÍ está\n\t\t// en la allowlist por defecto de todo subagente) sería una puerta trasera\n\t\t// a la memoria durable que ese bloque ya decidió cerrar.\n\t\tconst readBase = this._baseToolDefinitions.get(\"read\");\n\t\tif (readBase) {\n\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\"read\",\n\t\t\t\tenvolverReadConUrlsInternas(envolverReadConPdf(readBase, this._cwd), {\n\t\t\t\t\tmemoryEngine: this._isSubagentSession ? null : memoryEngine,\n\t\t\t\t\tvaultManager: this._resourceLoader.getVaultManager?.() ?? null,\n\t\t\t\t\tcwd: this._cwd,\n\t\t\t\t\tlibro: () => this._registro.libro(),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\t// Tools MCP: EXACTAMENTE DOS (mcp_list_tools, mcp_call_tool), nunca una por\n\t\t// herramienta remota — un servidor con 40 tools inflaría el system prompt y\n\t\t// desplazaría el prefijo cacheable. Solo sesión principal: un subagente no\n\t\t// debe poder alcanzar el Jira de la empresa.\n\t\tconst mcpManager = this._isSubagentSession ? null : this._resourceLoader.getMcpManager?.();\n\t\tif (mcpManager) {\n\t\t\tfor (const [name, tool] of Object.entries(createMcpTools(mcpManager))) {\n\t\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t\t}\n\t\t}\n\n\t\t// El registro de salidas se publica ANTES de crear las tools web, para que\n\t\t// también lo vean los caminos que no nacen aquí — hoy el cliente MCP, que\n\t\t// crea `resource-loader` y no tiene el libro a mano. Sin esto, el informe\n\t\t// para el DPO afirmaba «No hubo transferencias» sobre una sesión que había\n\t\t// hablado con un servidor de terceros. Ver `core/web/registro-red.ts`.\n\t\tpublicarAnotarRed((dominio, permitido, motivo) => {\n\t\t\tvoid this._registro.libro().anotar({ tipo: \"red\", dominio, permitido, motivo });\n\t\t});\n\n\t\t// Tools web. SÍ se registran en subagentes: `deep_research` delega la\n\t\t// búsqueda en ellos, y sin esto ese workflow pedía tools inexistentes.\n\t\t// El acceso no queda abierto: la allowlist del subagente solo las incluye\n\t\t// cuando el script pide `web: true` (ver workflows/agent-runner.ts).\n\t\tfor (const [name, tool] of Object.entries(\n\t\t\tcreateWebTools({\n\t\t\t\t// Vault primero; si está bloqueado o no trae el proveedor cae a la\n\t\t\t\t// variable de entorno, que es lo que usa CI. Un `nombreProveedor` por\n\t\t\t\t// llamada porque el registro prueba varios (brave, tavily, exa,\n\t\t\t\t// searxng, serper), no solo Serper.\n\t\t\t\tobtenerClave: crearObtenerClave(() => this._resourceLoader.getVaultManager?.()),\n\t\t\t\t// Libro de sesión: qué salió de la máquina y hacia dónde (art. 44 RGPD).\n\t\t\t\t// Solo el dominio; la ruta y la query pueden llevar tokens.\n\t\t\t\t// Se publica ademas para los caminos de salida que NO nacen aqui: el\n\t\t\t\t// cliente MCP lo crea `resource-loader`, que no tiene el libro a mano.\n\t\t\t\t// Ver `core/web/registro-red.ts`.\n\t\t\t\tanotarRed: (dominio, permitido, motivo) => {\n\t\t\t\t\tvoid this._registro.libro().anotar({ tipo: \"red\", dominio, permitido, motivo });\n\t\t\t\t},\n\t\t\t\t// Búsqueda con la credencial del modelo cuando el proveedor la trae.\n\t\t\t\t// Es lo que hace que `web_search` funcione sin configurar nada.\n\t\t\t\tbuscarNativo: async (consulta, maximo) => {\n\t\t\t\t\tconst modelo = this.model;\n\t\t\t\t\t// `null`, no `[]`: no es lo mismo no poder buscar que no encontrar.\n\t\t\t\t\tif (!modelo || !soportaBusquedaNativa(modelo.provider)) return null;\n\t\t\t\t\treturn buscarConElModelo(consulta, {\n\t\t\t\t\t\tmaxResultados: maximo,\n\t\t\t\t\t\tcompletar: async (\n\t\t\t\t\t\t\ttexto: string,\n\t\t\t\t\t\t\topciones: { fetch: typeof fetch; onPayload: (c: unknown) => unknown },\n\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\tawait this._modelRuntime.completeSimple(\n\t\t\t\t\t\t\t\tmodelo,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tsystemPrompt: \"Busca en la web lo que te pidan. Responde en una línea.\",\n\t\t\t\t\t\t\t\t\tmessages: [{ role: \"user\", content: texto }],\n\t\t\t\t\t\t\t\t} as never,\n\t\t\t\t\t\t\t\topciones as never,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t}),\n\t\t)) {\n\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t}\n\n\t\t// Búsqueda y edición estructural (`ast_search`/`ast_edit`). Sin condición,\n\t\t// como las tools web: no dependen de ningún subsistema externo, solo del\n\t\t// compilador de TypeScript que ya está en el bundle. Se registran también\n\t\t// en subagentes: un subagente de refactor necesita la misma precisión.\n\t\tfor (const [name, tool] of Object.entries(crearEstructuralTools(this._cwd))) {\n\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t}\n\n\t\t// Tools de navegador. Detrás de `computerUse.enabled`, que hasta ahora era\n\t\t// otro bloque de configuración con panel en el escritorio y nada detrás.\n\t\t// Registrarlas no arranca Chrome: el navegador se abre en la primera tool\n\t\t// que lo necesita. Para subagentes, además hay que pedir `browser: true`.\n\t\t// El navegador NO depende del control del ordenador, y antes sí: se pedía\n\t\t// `computerUse.enabled && computerUse.browser`. Eso dejaba la capacidad más\n\t\t// segura que tenemos —un Chrome con perfil vacío, sin sesiones iniciadas—\n\t\t// detrás del interruptor de la más peligrosa, que viene apagado. Resultado:\n\t\t// en una instalación recién hecha las tools de navegador ni siquiera se\n\t\t// registraban, y la regla del prompt \"interactuar con una web → browser_*\"\n\t\t// apuntaba al vacío.\n\t\t// La clave sigue viviendo bajo `computerUse` por compatibilidad con las\n\t\t// configuraciones que ya existen; lo que cambia es de qué depende.\n\t\tconst configMotor = this.settingsManager.getEngineConfig();\n\t\tif (configMotor.computerUse.browser) {\n\t\t\tfor (const [name, tool] of Object.entries(\n\t\t\t\tcrearBrowserTools({\n\t\t\t\t\tagentDir: this._resourceLoader.getAgentDir(),\n\t\t\t\t\t// Para que la regla de elección solo nombre lo que existe.\n\t\t\t\t\tordenador: configMotor.computerUse.enabled,\n\t\t\t\t}),\n\t\t\t)) {\n\t\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t\t}\n\t\t}\n\n\t\t// Control del ordenador. El motor es nuestro fork, y corre EN PROCESO: no\n\t\t// hay servidor MCP que registrar. El agente habla con NUESTRAS tools, que\n\t\t// aplican el techo por categoría y las prohibiciones antes de mover nada.\n\t\t// Es un interruptor, sin lista de aplicaciones que mantener: lo que\n\t\t// limita cada aplicación es su categoría, y se calcula sola.\n\t\tif (configMotor.computerUse.enabled) {\n\t\t\tfor (const [name, tool] of Object.entries(\n\t\t\t\tcrearComputerTools({ navegador: configMotor.computerUse.browser }),\n\t\t\t)) {\n\t\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t\t}\n\t\t}\n\n\t\t// Bash interactivo (bash_interactivo/bash_enviar/bash_cerrar). Sin\n\t\t// interruptor de settings a propósito: es la misma categoría de riesgo\n\t\t// que `bash` —pasa por la misma política de aprobación como `ejecucion`—\n\t\t// y ocultarla detrás de una opción solo dejaría al agente sin forma de\n\t\t// responder a un `sudo` o a un `rm -i` en las instalaciones por defecto.\n\t\tfor (const [name, tool] of Object.entries(crearInteractivoTools({ cwd: this._cwd }))) {\n\t\t\tthis._baseToolDefinitions.set(name, tool);\n\t\t}\n\n\t\t// Ada Intelligence Engine: tools del GoalEngine (resuelven el goal\n\t\t// activo por llamada). Registradas si el GoalEngine está habilitado.\n\t\t// Los subagentes NO reciben goal_*/workflow tools: solo el agente\n\t\t// principal orquesta el engine (anti-recursión y control de presupuesto).\n\t\tconst goalEngine = this._isSubagentSession ? null : this._resourceLoader.getGoalEngine?.();\n\t\tif (goalEngine && this.settingsManager.getGoalEngineEnabled()) {\n\t\t\tfor (const tool of goalSessionTools(goalEngine, () => this.sessionManager.getSessionId())) {\n\t\t\t\tthis._baseToolDefinitions.set(tool.name, tool as unknown as ToolDefinition);\n\t\t\t}\n\t\t\t// graph_add_nodes: registros puntuales (QA_FINDING/RISK/DECISION).\n\t\t\t// El spine se auto-registra vía GraphSync — esto es complemento.\n\t\t\tif (this.settingsManager.getGraphEnabled()) {\n\t\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\t\"graph_add_nodes\",\n\t\t\t\t\tcreateGraphTool(() => this._resourceLoader.getGraph?.()) as unknown as ToolDefinition,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Tool `workflow` SOLO en la sesión principal: los subagentes no\n\t\t// orquestan workflows (anti-anidamiento y control de presupuesto).\n\t\tif (!this._isSubagentSession) {\n\t\t\tthis._baseToolDefinitions.set(\n\t\t\t\t\"workflow\",\n\t\t\t\tcreateWorkflowTool({\n\t\t\t\t\tgetManager: () => this._resourceLoader.getWorkflowManager?.(),\n\t\t\t\t\tconfirm: (prompt) => this._confirmWorkflowLaunch(prompt),\n\t\t\t\t\tagentDir: this._resourceLoader.getAgentDir(),\n\t\t\t\t\tsessionId: () => this.sessionManager.getSessionId(),\n\t\t\t\t}) as unknown as ToolDefinition,\n\t\t\t);\n\t\t}\n\n\t\tconst extensionsResult = this._resourceLoader.getExtensions();\n\t\tif (options.flagValues) {\n\t\t\tfor (const [name, value] of options.flagValues) {\n\t\t\t\textensionsResult.runtime.flagValues.set(name, value);\n\t\t\t}\n\t\t}\n\n\t\tthis._extensionRunner = new ExtensionRunner(\n\t\t\textensionsResult.extensions,\n\t\t\textensionsResult.runtime,\n\t\t\tthis._cwd,\n\t\t\tthis.sessionManager,\n\t\t\tnew ModelRegistry(this._modelRuntime),\n\t\t);\n\t\tif (this._extensionRunnerRef) {\n\t\t\tthis._extensionRunnerRef.current = this._extensionRunner;\n\t\t}\n\t\tthis._bindExtensionCore(this._extensionRunner);\n\t\tthis._applyExtensionBindings(this._extensionRunner);\n\n\t\tconst defaultActiveToolNames = this._baseToolsOverride\n\t\t\t? Object.keys(this._baseToolsOverride)\n\t\t\t: [\"read\", \"bash\", \"edit\", \"write\"];\n\t\tconst baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;\n\t\tthis._refreshToolRegistry({\n\t\t\tactiveToolNames: baseActiveToolNames,\n\t\t\tincludeAllExtensionTools: options.includeAllExtensionTools,\n\t\t});\n\t}\n\n\tasync reload(options?: { beforeSessionStart?: () => void | Promise<void> }): Promise<void> {\n\t\tconst oldRunner = this._extensionRunner;\n\t\tconst previousFlagValues = oldRunner.getFlagValues();\n\t\tawait emitSessionShutdownEvent(oldRunner, { type: \"session_shutdown\", reason: \"reload\" });\n\t\toldRunner.invalidate();\n\t\tawait this.settingsManager.reload();\n\t\tthis.syncQueueModesFromSettings();\n\t\tresetApiProviders();\n\t\tawait this._resourceLoader.reload();\n\t\tthis._buildRuntime({\n\t\t\tactiveToolNames: this.getActiveToolNames(),\n\t\t\tflagValues: previousFlagValues,\n\t\t\tincludeAllExtensionTools: true,\n\t\t});\n\n\t\tconst hasBindings =\n\t\t\tthis._extensionUIContext ||\n\t\t\tthis._extensionCommandContextActions ||\n\t\t\tthis._extensionShutdownHandler ||\n\t\t\tthis._extensionErrorListener;\n\t\tif (hasBindings) {\n\t\t\tawait options?.beforeSessionStart?.();\n\t\t\tawait this._extensionRunner.emit({ type: \"session_start\", reason: \"reload\" });\n\t\t\tawait this.extendResourcesFromExtensions(\"reload\");\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Auto-Retry\n\t// =========================================================================\n\n\t/**\n\t * Check if an error is retryable (overloaded, rate limit, server errors).\n\t * Context overflow errors are NOT retryable (handled by compaction instead).\n\t */\n\tprivate _isRetryableError(message: AssistantMessage): boolean {\n\t\t// Context overflow is handled by compaction, not retry.\n\t\tif (isContextOverflow(message, this.model?.contextWindow ?? 0)) return false;\n\t\treturn isRetryableAssistantError(message);\n\t}\n\n\t/**\n\t * Retry policy + callbacks shared by compaction and branch-summary summarization calls.\n\t * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient\n\t * stream drop no longer fails the whole operation. `source` carries the context\n\t * the TUI needs to render the retry and recreate the underlying indicator.\n\t */\n\tprivate _summarizationRetryCallbacks(\n\t\tsource: { source: \"branchSummary\" } | { source: \"compaction\"; reason: \"manual\" | \"threshold\" | \"overflow\" },\n\t): RetryCallbacks {\n\t\treturn {\n\t\t\tonRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_scheduled\",\n\t\t\t\t\tattempt,\n\t\t\t\t\tmaxAttempts,\n\t\t\t\t\tdelayMs,\n\t\t\t\t\terrorMessage,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryAttemptStart: () => {\n\t\t\t\tthis._emit({\n\t\t\t\t\ttype: \"summarization_retry_attempt_start\",\n\t\t\t\t\t...source,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonRetryFinished: () => {\n\t\t\t\tthis._emit({ type: \"summarization_retry_finished\" });\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Prepare a retryable error for continuation with exponential backoff.\n\t * @returns true if the caller should continue the agent, false otherwise\n\t */\n\tprivate async _prepareRetry(message: AssistantMessage): Promise<boolean> {\n\t\tconst settings = this.settingsManager.getRetrySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis._retryAttempt++;\n\n\t\tif (this._retryAttempt > settings.maxRetries) {\n\t\t\t// Preserve the completed attempt count so post-run handling can emit the final failure.\n\t\t\tthis._retryAttempt--;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_start\",\n\t\t\tattempt: this._retryAttempt,\n\t\t\tmaxAttempts: settings.maxRetries,\n\t\t\tdelayMs,\n\t\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t\t});\n\n\t\t// Remove error message from agent state (keep in session for history)\n\t\tconst messages = this.agent.state.messages;\n\t\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t\t}\n\n\t\t// Wait with exponential backoff (abortable)\n\t\tthis._retryAbortController = new AbortController();\n\t\ttry {\n\t\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t\t} catch {\n\t\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\t\tconst attempt = this._retryAttempt;\n\t\t\tthis._retryAttempt = 0;\n\t\t\tthis._emit({\n\t\t\t\ttype: \"auto_retry_end\",\n\t\t\t\tsuccess: false,\n\t\t\t\tattempt,\n\t\t\t\tfinalError: \"Retry cancelled\",\n\t\t\t});\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tthis._retryAbortController = undefined;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Cancel in-progress retry.\n\t */\n\tabortRetry(): void {\n\t\tthis._retryAbortController?.abort();\n\t}\n\n\t/** Whether auto-retry is currently in progress */\n\tget isRetrying(): boolean {\n\t\treturn this._retryAbortController !== undefined;\n\t}\n\n\t/** Whether auto-retry is enabled */\n\tget autoRetryEnabled(): boolean {\n\t\treturn this.settingsManager.getRetryEnabled();\n\t}\n\n\t/**\n\t * Toggle auto-retry setting.\n\t */\n\tsetAutoRetryEnabled(enabled: boolean): void {\n\t\tthis.settingsManager.setRetryEnabled(enabled);\n\t}\n\n\t// =========================================================================\n\t// Bash Execution\n\t// =========================================================================\n\n\t/**\n\t * Execute a bash command.\n\t * Adds result to agent context and session.\n\t * @param command The bash command to execute\n\t * @param onChunk Optional streaming callback for output\n\t * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n\t * @param options.id Optional identifier included in bash execution update events\n\t * @param options.operations Custom BashOperations for remote execution\n\t */\n\tasync executeBash(\n\t\tcommand: string,\n\t\tonChunk?: (chunk: string) => void,\n\t\toptions?: { excludeFromContext?: boolean; id?: string; operations?: BashOperations },\n\t): Promise<BashResult> {\n\t\tconst abortController = new AbortController();\n\t\tthis._bashAbortControllers.add(abortController);\n\n\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\tconst prefix = this.settingsManager.getShellCommandPrefix();\n\t\tconst shellPath = this.settingsManager.getShellPath();\n\t\tconst resolvedCommand = prefix ? `${prefix}\\n${command}` : command;\n\n\t\ttry {\n\t\t\tconst result = await executeBashWithOperations(\n\t\t\t\tresolvedCommand,\n\t\t\t\tthis.sessionManager.getCwd(),\n\t\t\t\toptions?.operations ?? createLocalBashOperations({ shellPath }),\n\t\t\t\t{\n\t\t\t\t\tonChunk: (delta) => {\n\t\t\t\t\t\tonChunk?.(delta);\n\t\t\t\t\t\tthis._emit({ type: \"bash_execution_update\", id: options?.id, delta });\n\t\t\t\t\t},\n\t\t\t\t\tsignal: abortController.signal,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tthis.recordBashResult(command, result, options);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tthis._bashAbortControllers.delete(abortController);\n\t\t}\n\t}\n\n\t/**\n\t * Record a bash execution result in session history.\n\t * Used by executeBash and by extensions that handle bash execution themselves.\n\t */\n\trecordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean }): void {\n\t\tconst bashMessage: BashExecutionMessage = {\n\t\t\trole: \"bashExecution\",\n\t\t\tcommand,\n\t\t\toutput: result.output,\n\t\t\texitCode: result.exitCode,\n\t\t\tcancelled: result.cancelled,\n\t\t\ttruncated: result.truncated,\n\t\t\tfullOutputPath: result.fullOutputPath,\n\t\t\ttimestamp: Date.now(),\n\t\t\texcludeFromContext: options?.excludeFromContext,\n\t\t};\n\n\t\t// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering\n\t\tif (this.isStreaming) {\n\t\t\t// Queue for later - will be flushed on agent_end\n\t\t\tthis._pendingBashMessages.push(bashMessage);\n\t\t} else {\n\t\t\t// Add to agent state immediately\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.sessionManager.appendMessage(bashMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel running bash command.\n\t */\n\tabortBash(): void {\n\t\tfor (const abortController of [...this._bashAbortControllers]) {\n\t\t\tabortController.abort();\n\t\t}\n\t}\n\n\t/** Whether a bash command is currently running */\n\tget isBashRunning(): boolean {\n\t\treturn this._bashAbortControllers.size > 0;\n\t}\n\n\t/** Whether there are pending bash messages waiting to be flushed */\n\tget hasPendingBashMessages(): boolean {\n\t\treturn this._pendingBashMessages.length > 0;\n\t}\n\n\t/**\n\t * Flush pending bash messages to agent state and session.\n\t * Called after agent turn completes to maintain proper message ordering.\n\t */\n\tprivate _flushPendingBashMessages(): void {\n\t\tif (this._pendingBashMessages.length === 0) return;\n\n\t\tfor (const bashMessage of this._pendingBashMessages) {\n\t\t\t// Add to agent state\n\t\t\tthis.agent.state.messages.push(bashMessage);\n\n\t\t\t// Save to session\n\t\t\tthis.sessionManager.appendMessage(bashMessage);\n\t\t}\n\n\t\tthis._pendingBashMessages = [];\n\t}\n\n\t// =========================================================================\n\t// Session Management\n\t// =========================================================================\n\n\t/**\n\t * Set a display name for the current session.\n\t */\n\tsetSessionName(name: string): void {\n\t\tthis.sessionManager.appendSessionInfo(name);\n\t\tconst event = { type: \"session_info_changed\", name: this.sessionManager.getSessionName() } as const;\n\t\tthis._emit(event);\n\t\tvoid this._extensionRunner.emit(event);\n\t}\n\n\t// =========================================================================\n\t// Tree Navigation\n\t// =========================================================================\n\n\t/**\n\t * Navigate to a different node in the session tree.\n\t * Unlike fork() which creates a new session file, this stays in the same file.\n\t *\n\t * @param targetId The entry ID to navigate to\n\t * @param options.summarize Whether user wants to summarize abandoned branch\n\t * @param options.customInstructions Custom instructions for summarizer\n\t * @param options.replaceInstructions If true, customInstructions replaces the default prompt\n\t * @param options.label Label to attach to the branch summary entry\n\t * @returns Result with editorText (if user message) and cancelled status\n\t */\n\tasync navigateTree(\n\t\ttargetId: string,\n\t\toptions: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string } = {},\n\t): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry }> {\n\t\tif (this.isStreaming) {\n\t\t\tthrow new Error(\"Wait for the current response to finish before navigating the session tree.\");\n\t\t}\n\n\t\tconst oldLeafId = this.sessionManager.getLeafId();\n\n\t\t// No-op if already at target\n\t\tif (targetId === oldLeafId) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\t// Model required for summarization\n\t\tif (options.summarize && !this.model) {\n\t\t\tthrow new Error(\"No model available for summarization\");\n\t\t}\n\n\t\tconst targetEntry = this.sessionManager.getEntry(targetId);\n\t\tif (!targetEntry) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\n\t\t// Collect entries to summarize (from old leaf to common ancestor)\n\t\tconst { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(\n\t\t\tthis.sessionManager,\n\t\t\toldLeafId,\n\t\t\ttargetId,\n\t\t);\n\n\t\t// Prepare event data - mutable so extensions can override\n\t\tlet customInstructions = options.customInstructions;\n\t\tlet replaceInstructions = options.replaceInstructions;\n\t\tlet label = options.label;\n\n\t\tconst preparation: TreePreparation = {\n\t\t\ttargetId,\n\t\t\toldLeafId,\n\t\t\tcommonAncestorId,\n\t\t\tentriesToSummarize,\n\t\t\tuserWantsSummary: options.summarize ?? false,\n\t\t\tcustomInstructions,\n\t\t\treplaceInstructions,\n\t\t\tlabel,\n\t\t};\n\n\t\t// Set up abort controller for summarization\n\t\tthis._branchSummaryAbortController = new AbortController();\n\n\t\ttry {\n\t\t\tlet extensionSummary: { summary: string; details?: unknown; usage?: Usage } | undefined;\n\t\t\tlet fromExtension = false;\n\n\t\t\t// Emit session_before_tree event\n\t\t\tif (this._extensionRunner.hasHandlers(\"session_before_tree\")) {\n\t\t\t\tconst result = (await this._extensionRunner.emit({\n\t\t\t\t\ttype: \"session_before_tree\",\n\t\t\t\t\tpreparation,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t})) as SessionBeforeTreeResult | undefined;\n\n\t\t\t\tif (result?.cancel) {\n\t\t\t\t\treturn { cancelled: true };\n\t\t\t\t}\n\n\t\t\t\tif (result?.summary && options.summarize) {\n\t\t\t\t\textensionSummary = result.summary;\n\t\t\t\t\tfromExtension = true;\n\t\t\t\t}\n\n\t\t\t\t// Allow extensions to override instructions and label\n\t\t\t\tif (result?.customInstructions !== undefined) {\n\t\t\t\t\tcustomInstructions = result.customInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.replaceInstructions !== undefined) {\n\t\t\t\t\treplaceInstructions = result.replaceInstructions;\n\t\t\t\t}\n\t\t\t\tif (result?.label !== undefined) {\n\t\t\t\t\tlabel = result.label;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Run default summarizer if needed\n\t\t\tlet summaryText: string | undefined;\n\t\t\tlet summaryDetails: unknown;\n\t\t\tlet summaryUsage: Usage | undefined;\n\t\t\tif (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {\n\t\t\t\tconst model = this.model!;\n\t\t\t\tconst { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);\n\t\t\t\tconst branchSummarySettings = this.settingsManager.getBranchSummarySettings();\n\t\t\t\tconst result = await generateBranchSummary(entriesToSummarize, {\n\t\t\t\t\tmodel: requestModel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\theaders,\n\t\t\t\t\tenv,\n\t\t\t\t\tsignal: this._branchSummaryAbortController.signal,\n\t\t\t\t\tcustomInstructions,\n\t\t\t\t\treplaceInstructions,\n\t\t\t\t\treserveTokens: branchSummarySettings.reserveTokens,\n\t\t\t\t\tstreamFn: this.agent.streamFunction,\n\t\t\t\t\tretry: this.settingsManager.getRetrySettings(),\n\t\t\t\t\tcallbacks: this._summarizationRetryCallbacks({ source: \"branchSummary\" }),\n\t\t\t\t});\n\t\t\t\tif (result.aborted) {\n\t\t\t\t\treturn { cancelled: true, aborted: true };\n\t\t\t\t}\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthrow new Error(result.error);\n\t\t\t\t}\n\t\t\t\tsummaryText = result.summary;\n\t\t\t\tsummaryUsage = result.usage;\n\t\t\t\tsummaryDetails = {\n\t\t\t\t\treadFiles: result.readFiles || [],\n\t\t\t\t\tmodifiedFiles: result.modifiedFiles || [],\n\t\t\t\t};\n\t\t\t} else if (extensionSummary) {\n\t\t\t\tsummaryText = extensionSummary.summary;\n\t\t\t\tsummaryDetails = extensionSummary.details;\n\t\t\t\tsummaryUsage = extensionSummary.usage;\n\t\t\t}\n\n\t\t\t// Determine the new leaf position based on target type\n\t\t\tlet newLeafId: string | null;\n\t\t\tlet editorText: string | undefined;\n\n\t\t\tif (targetEntry.type === \"message\" && targetEntry.message.role === \"user\") {\n\t\t\t\t// User message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = contentText(targetEntry.message.content, \"\");\n\t\t\t} else if (targetEntry.type === \"custom_message\") {\n\t\t\t\t// Custom message: leaf = parent (null if root), text goes to editor\n\t\t\t\tnewLeafId = targetEntry.parentId;\n\t\t\t\teditorText = contentText(targetEntry.content, \"\");\n\t\t\t} else {\n\t\t\t\t// Non-user message: leaf = selected node\n\t\t\t\tnewLeafId = targetId;\n\t\t\t}\n\n\t\t\t// Switch leaf (with or without summary)\n\t\t\t// Summary is attached at the navigation target position (newLeafId), not the old branch\n\t\t\tlet summaryEntry: BranchSummaryEntry | undefined;\n\t\t\tif (summaryText) {\n\t\t\t\t// Create summary at target position (can be null for root)\n\t\t\t\tconst summaryId = this.sessionManager.branchWithSummary(\n\t\t\t\t\tnewLeafId,\n\t\t\t\t\tsummaryText,\n\t\t\t\t\tsummaryDetails,\n\t\t\t\t\tfromExtension,\n\t\t\t\t\tsummaryUsage,\n\t\t\t\t);\n\t\t\t\tsummaryEntry = this.sessionManager.getEntry(summaryId) as BranchSummaryEntry;\n\n\t\t\t\t// Attach label to the summary entry\n\t\t\t\tif (label) {\n\t\t\t\t\tthis.sessionManager.appendLabelChange(summaryId, label);\n\t\t\t\t}\n\t\t\t} else if (newLeafId === null) {\n\t\t\t\t// No summary, navigating to root - reset leaf\n\t\t\t\tthis.sessionManager.resetLeaf();\n\t\t\t} else {\n\t\t\t\t// No summary, navigating to non-root\n\t\t\t\tthis.sessionManager.branch(newLeafId);\n\t\t\t}\n\n\t\t\t// Attach label to target entry when not summarizing (no summary entry to label)\n\t\t\tif (label && !summaryText) {\n\t\t\t\tthis.sessionManager.appendLabelChange(targetId, label);\n\t\t\t}\n\n\t\t\t// Update agent state\n\t\t\tconst sessionContext = this.sessionManager.buildSessionContext();\n\t\t\tthis.agent.state.messages = sessionContext.messages;\n\n\t\t\t// Emit session_tree event\n\t\t\tawait this._extensionRunner.emit({\n\t\t\t\ttype: \"session_tree\",\n\t\t\t\tnewLeafId: this.sessionManager.getLeafId(),\n\t\t\t\toldLeafId,\n\t\t\t\tsummaryEntry,\n\t\t\t\tfromExtension: summaryText ? fromExtension : undefined,\n\t\t\t});\n\n\t\t\t// Emit to custom tools\n\n\t\t\treturn { editorText, cancelled: false, summaryEntry };\n\t\t} finally {\n\t\t\tthis._branchSummaryAbortController = undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Get all user messages from session for fork selector.\n\t */\n\tgetUserMessagesForForking(): Array<{ entryId: string; text: string }> {\n\t\tconst entries = this.sessionManager.getEntries();\n\t\tconst result: Array<{ entryId: string; text: string }> = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tif (entry.message.role !== \"user\") continue;\n\n\t\t\tconst text = contentText(entry.message.content, \"\");\n\t\t\tif (text) {\n\t\t\t\tresult.push({ entryId: entry.id, text });\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get session statistics. Aggregates over ALL session entries (including\n\t * history that was compacted away), so token/cost totals reflect what was\n\t * actually billed across the session.\n\t */\n\tgetSessionStats(): SessionStats {\n\t\tlet userMessages = 0;\n\t\tlet assistantMessages = 0;\n\t\tlet toolResults = 0;\n\t\tlet totalMessages = 0;\n\t\tlet toolCalls = 0;\n\t\tconst usageTotals = createUsageTotals();\n\n\t\tfor (const entry of this.sessionManager.getEntries()) {\n\t\t\tif ((entry.type === \"branch_summary\" || entry.type === \"compaction\") && entry.usage) {\n\t\t\t\taddUsageToTotals(usageTotals, entry.usage);\n\t\t\t}\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\ttotalMessages++;\n\t\t\tconst message = entry.message;\n\t\t\tif (message.role === \"user\") {\n\t\t\t\tuserMessages++;\n\t\t\t} else if (message.role === \"toolResult\") {\n\t\t\t\ttoolResults++;\n\t\t\t\tif (message.usage) {\n\t\t\t\t\taddUsageToTotals(usageTotals, message.usage);\n\t\t\t\t}\n\t\t\t} else if (message.role === \"assistant\") {\n\t\t\t\tassistantMessages++;\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\tif (Array.isArray(assistantMsg.content)) {\n\t\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\t}\n\t\t\t\taddUsageToTotals(usageTotals, assistantMsg.usage);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tcontextBreakdown: this.getContextBreakdown(),\n\t\t\tsessionFile: this.sessionFile,\n\t\t\tsessionId: this.sessionId,\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages,\n\t\t\ttokens: {\n\t\t\t\tinput: usageTotals.input,\n\t\t\t\toutput: usageTotals.output,\n\t\t\t\tcacheRead: usageTotals.cacheRead,\n\t\t\t\tcacheWrite: usageTotals.cacheWrite,\n\t\t\t\ttotal: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,\n\t\t\t},\n\t\t\tcost: usageTotals.cost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t};\n\t}\n\n\t/**\n\t * Desglose de qué llena la ventana de contexto.\n\t *\n\t * `getContextUsage()` da un único número; esto dice de dónde sale. Con caché\n\t * por prefijo eso no es curiosidad: el coste de tocar algo depende de su\n\t * posición, así que saber que las definiciones de tools ocupan 11 K y van\n\t * delante te dice que cambiarlas invalida todo lo de detrás.\n\t *\n\t * El total lo aporta el proveedor y es exacto; el reparto se estima. La\n\t * distinción viaja en `totalExacto` para que la interfaz no presente\n\t * estimaciones como medidas.\n\t */\n\tgetContextBreakdown(): import(\"./context-breakdown.ts\").DesgloseContexto | undefined {\n\t\tconst model = this.model;\n\t\tif (!model?.contextWindow) return undefined;\n\t\tconst usage = this.getContextUsage();\n\n\t\tconst opciones = this._baseSystemPromptOptions;\n\t\t// Los bloques de memoria y del harness llegan como ficheros de contexto con\n\t\t// una ruta marcada; se separan porque son las dos palancas que el usuario\n\t\t// puede apagar desde ajustes.\n\t\tconst esMemoria = (ruta: string) => ruta.startsWith(\"<ada-\") || ruta.includes(\"memory\");\n\t\tconst rutas = opciones?.contextFiles ?? [];\n\n\t\tconst definiciones: string[] = [];\n\t\tconst mcp: string[] = [];\n\t\tfor (const [nombre, { definition }] of this._toolDefinitions) {\n\t\t\t// Nombre + descripción + esquema es lo que de verdad viaja al modelo.\n\t\t\tconst texto = `${nombre}${definition.description ?? \"\"}${JSON.stringify(definition.parameters ?? {})}`;\n\t\t\tif (nombre.startsWith(\"mcp_\")) mcp.push(texto);\n\t\t\telse definiciones.push(texto);\n\t\t}\n\n\t\tconst skills = this._resourceLoader.getSkills().skills.map((skill) => `${skill.name}${skill.description ?? \"\"}`);\n\n\t\treturn construirDesglose({\n\t\t\tcontextWindow: model.contextWindow,\n\t\t\ttokensReales: usage?.tokens ?? null,\n\t\t\tsystemPrompt: this.systemPrompt ?? \"\",\n\t\t\ttoolDefinitions: definiciones,\n\t\t\tskills,\n\t\t\tmcpTools: mcp,\n\t\t\tcontextFiles: rutas.filter((f) => !esMemoria(f.path)).map((f) => f.content),\n\t\t\tmemory: rutas.filter((f) => esMemoria(f.path)).map((f) => f.content),\n\t\t\tconversationTokens: this._estimarTokensConversacion(),\n\t\t});\n\t}\n\n\t/** Tokens de los mensajes del hilo activo, con el mismo estimador que compacta. */\n\tprivate _estimarTokensConversacion(): number {\n\t\tlet total = 0;\n\t\tfor (const entrada of this.sessionManager.getBranch()) {\n\t\t\tif (entrada.type === \"message\") total += estimateTokens(entrada.message);\n\t\t}\n\t\treturn total;\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.model;\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// We can only trust usage from an assistant that responded after the latest compaction.\n\t\t// If no such assistant exists, context token count is unknown until the next LLM response.\n\t\tconst branchEntries = this.sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\n\t\tif (latestCompaction) {\n\t\t\t// Check if there's a valid assistant usage after the compaction boundary\n\t\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\t\tlet hasPostCompactionUsage = false;\n\t\t\tfor (let i = branchEntries.length - 1; i > compactionIndex; i--) {\n\t\t\t\tconst entry = branchEntries[i];\n\t\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\") {\n\t\t\t\t\t\tconst contextTokens = calculateContextTokens(assistant.usage);\n\t\t\t\t\t\tif (contextTokens > 0) {\n\t\t\t\t\t\t\thasPostCompactionUsage = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasPostCompactionUsage) {\n\t\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t\t}\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.messages);\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @param options Optional export presentation settings\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string, options: { themeName?: string } = {}): Promise<string> {\n\t\tconst themeName = [options.themeName, this.settingsManager.getTheme()].find(\n\t\t\t(candidate) => candidate !== undefined && getThemeByName(candidate) !== undefined,\n\t\t);\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(this.sessionManager, this.state, {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: this.sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = this.sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t// =========================================================================\n\t// Utilities\n\t// =========================================================================\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.messages\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n\n\t// =========================================================================\n\t// Extension System\n\t// =========================================================================\n\n\tcreateReplacedSessionContext(): ReplacedSessionContext {\n\t\tconst context = Object.defineProperties(\n\t\t\t{},\n\t\t\tObject.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()),\n\t\t) as ReplacedSessionContext;\n\t\tcontext.sendMessage = (message, options) => this.sendCustomMessage(message, options);\n\t\tcontext.sendUserMessage = (content, options) => this.sendUserMessage(content, options);\n\t\treturn context;\n\t}\n\n\t/**\n\t * Check if extensions have handlers for a specific event type.\n\t */\n\thasExtensionHandlers(eventType: string): boolean {\n\t\treturn this._extensionRunner.hasHandlers(eventType);\n\t}\n\n\t/**\n\t * Get the extension runner (for setting UI context and error handlers).\n\t */\n\tget extensionRunner(): ExtensionRunner {\n\t\treturn this._extensionRunner;\n\t}\n}\n"]}