zidane 9.1.1 → 9.1.3

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 (279) hide show
  1. package/dist/{acp-Di9WvMDF.js → acp-CeCavNlg.js} +7 -7
  2. package/dist/{acp-Di9WvMDF.js.map → acp-CeCavNlg.js.map} +1 -1
  3. package/dist/acp-cli.js +5 -5
  4. package/dist/acp.d.ts +2 -2
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-DJhpTS-B.js → agent-Dh_XZ7fj.js} +74 -19
  7. package/dist/agent-Dh_XZ7fj.js.map +1 -0
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/{auth-store-port-DFgf22oo.js → auth-store-port-BKpjlJ8a.js} +2 -2
  11. package/dist/{auth-store-port-DFgf22oo.js.map → auth-store-port-BKpjlJ8a.js.map} +1 -1
  12. package/dist/{auto-update-restart-BFB7Ek87.js → auto-update-restart-CQJtndMG.js} +277 -41
  13. package/dist/auto-update-restart-CQJtndMG.js.map +1 -0
  14. package/dist/{browser-BcoCCB_n.js → browser-BkdeKnxO.js} +56 -2
  15. package/dist/browser-BkdeKnxO.js.map +1 -0
  16. package/dist/{browser-entry-ypi8CyE-.d.ts → browser-entry-BCwruqNa.d.ts} +10 -10
  17. package/dist/{browser-entry-ypi8CyE-.d.ts.map → browser-entry-BCwruqNa.d.ts.map} +1 -1
  18. package/dist/channels.d.ts +5 -5
  19. package/dist/chat/browser.d.ts +4 -4
  20. package/dist/chat/browser.js +6 -6
  21. package/dist/chat/pure.d.ts +5 -5
  22. package/dist/chat/pure.js +8 -8
  23. package/dist/chat.d.ts +5 -5
  24. package/dist/chat.js +55 -55
  25. package/dist/chat.js.map +1 -1
  26. package/dist/{cli-shared-CabruH8Z.js → cli-shared-DeRHgYvl.js} +4 -4
  27. package/dist/{cli-shared-CabruH8Z.js.map → cli-shared-DeRHgYvl.js.map} +1 -1
  28. package/dist/cli-style-DBEm6CyT.js +105 -0
  29. package/dist/cli-style-DBEm6CyT.js.map +1 -0
  30. package/dist/{client-core-CIixwznM.d.ts → client-core-C_4m_oL6.d.ts} +4 -4
  31. package/dist/{client-core-CIixwznM.d.ts.map → client-core-C_4m_oL6.d.ts.map} +1 -1
  32. package/dist/{client-CCaIXWue.d.ts → client-ygTASGis.d.ts} +2 -2
  33. package/dist/{client-CCaIXWue.d.ts.map → client-ygTASGis.d.ts.map} +1 -1
  34. package/dist/codemode-worker.js +8 -5
  35. package/dist/codemode-worker.js.map +1 -1
  36. package/dist/codemode.d.ts +16 -0
  37. package/dist/codemode.d.ts.map +1 -1
  38. package/dist/codemode.js +2 -2
  39. package/dist/{connection-loop-QmWL-fJf.d.ts → connection-loop-D_pRONCr.d.ts} +4 -4
  40. package/dist/{connection-loop-QmWL-fJf.d.ts.map → connection-loop-D_pRONCr.d.ts.map} +1 -1
  41. package/dist/contexts/daytona.d.ts +3 -3
  42. package/dist/contexts/docker.d.ts +1 -1
  43. package/dist/contexts/e2b.d.ts +2 -2
  44. package/dist/contexts/sandbox.d.ts +1 -1
  45. package/dist/{contexts-C72b0mMa.js → contexts-DLZTSRHU.js} +17 -106
  46. package/dist/contexts-DLZTSRHU.js.map +1 -0
  47. package/dist/contexts.d.ts +4 -4
  48. package/dist/contexts.js +2 -2
  49. package/dist/{credentials-wwETW8rI.js → credentials-Bqg-qNBM.js} +2 -2
  50. package/dist/{credentials-wwETW8rI.js.map → credentials-Bqg-qNBM.js.map} +1 -1
  51. package/dist/{cwd-picker-CcekODCt.js → cwd-picker-BsHA6HKv.js} +4 -4
  52. package/dist/{cwd-picker-CcekODCt.js.map → cwd-picker-BsHA6HKv.js.map} +1 -1
  53. package/dist/daemon-cli.js +4 -3
  54. package/dist/daemon-cli.js.map +1 -1
  55. package/dist/{decisions-CXmK0nz9.js → decisions-hsw5XS2g.js} +3 -1
  56. package/dist/decisions-hsw5XS2g.js.map +1 -0
  57. package/dist/{discovery-DycXFxdA.js → discovery-CX7nbMCn.js} +4 -4
  58. package/dist/{discovery-DycXFxdA.js.map → discovery-CX7nbMCn.js.map} +1 -1
  59. package/dist/durable/sqlite.d.ts +2 -2
  60. package/dist/durable.d.ts +1 -1
  61. package/dist/{engine-VQ86XGdG.js → engine-CxBGgU94.js} +6 -6
  62. package/dist/{engine-VQ86XGdG.js.map → engine-CxBGgU94.js.map} +1 -1
  63. package/dist/{eval-Bod0I1W_.js → eval-CfGUDAkN.js} +3 -3
  64. package/dist/{eval-Bod0I1W_.js.map → eval-CfGUDAkN.js.map} +1 -1
  65. package/dist/eval.d.ts +1 -1
  66. package/dist/eval.js +1 -1
  67. package/dist/{executor-C7RQrkEq.js → executor-j_LFRBwS.js} +4 -4
  68. package/dist/{executor-C7RQrkEq.js.map → executor-j_LFRBwS.js.map} +1 -1
  69. package/dist/{extension-mark-BqKdEZwF.js → extension-mark-_rhkDWL5.js} +9 -6
  70. package/dist/extension-mark-_rhkDWL5.js.map +1 -0
  71. package/dist/extensions/testing.d.ts +1 -1
  72. package/dist/extensions/testing.js +2 -2
  73. package/dist/{extensions-BCx4QuGB.js → extensions-ADlhBQ_1.js} +8 -8
  74. package/dist/{extensions-BCx4QuGB.js.map → extensions-ADlhBQ_1.js.map} +1 -1
  75. package/dist/{extensions-client-D8tEAcMw.d.ts → extensions-client-D4Q2XRKV.d.ts} +31 -5
  76. package/dist/extensions-client-D4Q2XRKV.d.ts.map +1 -0
  77. package/dist/extensions.d.ts +1 -1
  78. package/dist/extensions.js +6 -6
  79. package/dist/{fold-rules-DvLK8pbf.d.ts → fold-rules-1DpDwG9h.d.ts} +2 -2
  80. package/dist/{fold-rules-DvLK8pbf.d.ts.map → fold-rules-1DpDwG9h.d.ts.map} +1 -1
  81. package/dist/{google-Crva32sk.js → google-B-Dhmzzf.js} +2 -2
  82. package/dist/{google-Crva32sk.js.map → google-B-Dhmzzf.js.map} +1 -1
  83. package/dist/{headless-CCm-jLLT.js → headless-CQm19BL0.js} +4 -4
  84. package/dist/{headless-CCm-jLLT.js.map → headless-CQm19BL0.js.map} +1 -1
  85. package/dist/headless.d.ts +1 -1
  86. package/dist/headless.js +1 -1
  87. package/dist/history-worker.js +1 -1
  88. package/dist/{host-wO_6GW0y.js → host-C61ntFBC.js} +276 -103
  89. package/dist/host-C61ntFBC.js.map +1 -0
  90. package/dist/{host-BHMs9jsN.js → host-DEoEzQal.js} +13 -4
  91. package/dist/host-DEoEzQal.js.map +1 -0
  92. package/dist/{host-scoped-settings-CrbcqOkP.js → host-scoped-settings-WBYaCork.js} +2 -2
  93. package/dist/{host-scoped-settings-CrbcqOkP.js.map → host-scoped-settings-WBYaCork.js.map} +1 -1
  94. package/dist/{host-signals-Jsjd_-4g.js → host-signals-Cb2TlGtN.js} +5 -5
  95. package/dist/{host-signals-Jsjd_-4g.js.map → host-signals-Cb2TlGtN.js.map} +1 -1
  96. package/dist/{host-version-CNAr9pov.js → host-version-BIx4CuBM.js} +2 -2
  97. package/dist/{host-version-CNAr9pov.js.map → host-version-BIx4CuBM.js.map} +1 -1
  98. package/dist/{index-C6nnKu32.d.ts → index-B0PLo9jn.d.ts} +4 -4
  99. package/dist/{index-C6nnKu32.d.ts.map → index-B0PLo9jn.d.ts.map} +1 -1
  100. package/dist/{index-Djnc8Peh.d.ts → index-De4x-s76.d.ts} +14 -14
  101. package/dist/{index-Djnc8Peh.d.ts.map → index-De4x-s76.d.ts.map} +1 -1
  102. package/dist/{index-CGbOWa7y.d.ts → index-WlE93Q6d.d.ts} +411 -3
  103. package/dist/index-WlE93Q6d.d.ts.map +1 -0
  104. package/dist/{index-CCf_WeTQ.d.ts → index-f-XWIoYo.d.ts} +2 -2
  105. package/dist/{index-CCf_WeTQ.d.ts.map → index-f-XWIoYo.d.ts.map} +1 -1
  106. package/dist/index.d.ts +5 -5
  107. package/dist/index.js +17 -17
  108. package/dist/ipc/browser-types.d.ts +304 -7
  109. package/dist/ipc/browser-types.d.ts.map +1 -1
  110. package/dist/ipc/browser.d.ts +4 -4
  111. package/dist/ipc/browser.js +2 -2
  112. package/dist/ipc/remote.d.ts +6 -6
  113. package/dist/ipc/remote.js +3 -3
  114. package/dist/{ipc-BzWhIybr.js → ipc-DiH_P-wr.js} +4 -4
  115. package/dist/{ipc-BzWhIybr.js.map → ipc-DiH_P-wr.js.map} +1 -1
  116. package/dist/ipc.d.ts +10 -10
  117. package/dist/ipc.js +8 -8
  118. package/dist/{json-Dj-YATkZ.js → json-BjClr43H.js} +2 -20
  119. package/dist/json-BjClr43H.js.map +1 -0
  120. package/dist/{links-CJG_rFvf.js → links-BbC_lOnn.js} +9 -9
  121. package/dist/{links-CJG_rFvf.js.map → links-BbC_lOnn.js.map} +1 -1
  122. package/dist/{loaded-entry-DTURJo_R.js → loaded-entry-C0vkCVNQ.js} +2 -2
  123. package/dist/{loaded-entry-DTURJo_R.js.map → loaded-entry-C0vkCVNQ.js.map} +1 -1
  124. package/dist/mcp.d.ts +1 -1
  125. package/dist/{media-type-BtA0cbfI.d.ts → media-type-ZSmAGHvg.d.ts} +188 -7
  126. package/dist/media-type-ZSmAGHvg.d.ts.map +1 -0
  127. package/dist/{memory-DuVGrzbl.js → memory-C3w8nO8f.js} +132 -31
  128. package/dist/memory-C3w8nO8f.js.map +1 -0
  129. package/dist/model-test-CAtJ76kM.js.map +1 -1
  130. package/dist/{move-session-kylExJ5H.js → move-session-D7ILECs-.js} +4 -4
  131. package/dist/{move-session-kylExJ5H.js.map → move-session-D7ILECs-.js.map} +1 -1
  132. package/dist/{nested-call-record-DlzPeTB7.js → nested-call-record-OayszZBQ.js} +4 -3
  133. package/dist/nested-call-record-OayszZBQ.js.map +1 -0
  134. package/dist/output/stream-json.d.ts +1 -1
  135. package/dist/output/terminal.d.ts +1 -1
  136. package/dist/{path-display-COVV71Yu.js → path-display-DRAEpKOU.js} +3 -3
  137. package/dist/{path-display-COVV71Yu.js.map → path-display-DRAEpKOU.js.map} +1 -1
  138. package/dist/plugins.d.ts +1 -1
  139. package/dist/{prelude-CVKoUzV8.js → prelude-BwiSR1uA.js} +16 -5
  140. package/dist/prelude-BwiSR1uA.js.map +1 -0
  141. package/dist/{presets-CBt6rFfS.js → presets-CMY43nUP.js} +3 -3
  142. package/dist/{presets-CBt6rFfS.js.map → presets-CMY43nUP.js.map} +1 -1
  143. package/dist/presets.d.ts +1 -1
  144. package/dist/presets.js +1 -1
  145. package/dist/{protocol-CEVqBO-q.d.ts → protocol-C2lqKuMd.d.ts} +50 -7
  146. package/dist/protocol-C2lqKuMd.d.ts.map +1 -0
  147. package/dist/protocol-DOGyHgLQ.js.map +1 -1
  148. package/dist/protocol-NSllElLw.js.map +1 -1
  149. package/dist/providers/anthropic.d.ts +1 -1
  150. package/dist/providers/arcee.d.ts +1 -1
  151. package/dist/providers/baseten.d.ts +1 -1
  152. package/dist/providers/cerebras.d.ts +1 -1
  153. package/dist/providers/google.d.ts +1 -1
  154. package/dist/providers/google.js +1 -1
  155. package/dist/providers/local.d.ts +1 -1
  156. package/dist/providers/neuralwatt.d.ts +1 -1
  157. package/dist/providers/openai-compat.d.ts +1 -1
  158. package/dist/providers/openai.d.ts +1 -1
  159. package/dist/providers/openrouter.d.ts +1 -1
  160. package/dist/providers/xai.d.ts +1 -1
  161. package/dist/{providers-Z0TaBp1O.js → providers-CpXV_uLE.js} +5 -5
  162. package/dist/providers-CpXV_uLE.js.map +1 -0
  163. package/dist/providers.d.ts +1 -1
  164. package/dist/providers.js +1 -1
  165. package/dist/{readonly-db-D7Z-k0pQ.js → readonly-db-DrbT8tX6.js} +2 -2
  166. package/dist/{readonly-db-D7Z-k0pQ.js.map → readonly-db-DrbT8tX6.js.map} +1 -1
  167. package/dist/{registry-BKjeBnoy.js → registry-CJHvmvaP.js} +2 -2
  168. package/dist/{registry-BKjeBnoy.js.map → registry-CJHvmvaP.js.map} +1 -1
  169. package/dist/{registry-B6Ww76iW.js → registry-CN6JlZKj.js} +2 -2
  170. package/dist/{registry-B6Ww76iW.js.map → registry-CN6JlZKj.js.map} +1 -1
  171. package/dist/{registry-B9INJwJR.js → registry-CnnKaY9s.js} +324 -39
  172. package/dist/registry-CnnKaY9s.js.map +1 -0
  173. package/dist/{remote-hosts-BMWoHCWO.js → remote-hosts-DoO9CJUQ.js} +4 -4
  174. package/dist/{remote-hosts-BMWoHCWO.js.map → remote-hosts-DoO9CJUQ.js.map} +1 -1
  175. package/dist/{repo-url-D4Dr8NfZ.js → repo-url-DLjjuVk_.js} +169 -2
  176. package/dist/repo-url-DLjjuVk_.js.map +1 -0
  177. package/dist/restate.d.ts +2 -2
  178. package/dist/{sandbox-BwiPGKF7.d.ts → sandbox-B4OW6oNx.d.ts} +2 -2
  179. package/dist/{sandbox-BwiPGKF7.d.ts.map → sandbox-B4OW6oNx.d.ts.map} +1 -1
  180. package/dist/script-progress-CrZ507Qq.js +900 -0
  181. package/dist/script-progress-CrZ507Qq.js.map +1 -0
  182. package/dist/session/sqlite.d.ts +1 -1
  183. package/dist/session.d.ts +1 -1
  184. package/dist/{setting-BQBSyUq0.js → setting-D_FpKFMw.js} +6 -6
  185. package/dist/{setting-BQBSyUq0.js.map → setting-D_FpKFMw.js.map} +1 -1
  186. package/dist/{settings-catalog-4IU2i_jG.js → settings-catalog-DEZlXP51.js} +14 -2
  187. package/dist/settings-catalog-DEZlXP51.js.map +1 -0
  188. package/dist/{settings-keys-iI_Y-7MC.js → settings-keys-CW2UgHgb.js} +3 -3
  189. package/dist/{settings-keys-iI_Y-7MC.js.map → settings-keys-CW2UgHgb.js.map} +1 -1
  190. package/dist/{skills-CFXRItJ6.js → skills-CEbosUlM.js} +2 -2
  191. package/dist/{skills-CFXRItJ6.js.map → skills-CEbosUlM.js.map} +1 -1
  192. package/dist/skills.d.ts +1 -1
  193. package/dist/skills.js +1 -1
  194. package/dist/{source-Bl1Z501r.js → source-3ZnEPOSa.js} +3 -2
  195. package/dist/source-3ZnEPOSa.js.map +1 -0
  196. package/dist/{sqlite-runtime-BZ2L4RaG.d.ts → sqlite-runtime-CS_LBaAN.d.ts} +2 -2
  197. package/dist/sqlite-runtime-CS_LBaAN.d.ts.map +1 -0
  198. package/dist/{src-BY_ND2xn.js → src-BmTXDta8.js} +2 -2
  199. package/dist/{src-BY_ND2xn.js.map → src-BmTXDta8.js.map} +1 -1
  200. package/dist/{store-C6gI7FfZ.js → store-ByCDaKgJ.js} +14 -3
  201. package/dist/store-ByCDaKgJ.js.map +1 -0
  202. package/dist/{system-freeze-m5o1sGIM.js → system-freeze-XgDPIwQS.js} +3 -3
  203. package/dist/{system-freeze-m5o1sGIM.js.map → system-freeze-XgDPIwQS.js.map} +1 -1
  204. package/dist/{todo-edits-CbukbjyV.js → todo-edits-Dyj32ds6.js} +4 -3
  205. package/dist/todo-edits-Dyj32ds6.js.map +1 -0
  206. package/dist/tools/fetch-url.d.ts +1 -1
  207. package/dist/tools/web-search.d.ts +1 -1
  208. package/dist/{tools-CVTGhggj.js → tools-BsrGQ8K4.js} +2 -2
  209. package/dist/{tools-CVTGhggj.js.map → tools-BsrGQ8K4.js.map} +1 -1
  210. package/dist/tools.d.ts +1 -1
  211. package/dist/tools.js +3 -3
  212. package/dist/{transcript-anchors-CiSi_UBn.js → transcript-anchors-DMjvyUjk.js} +3 -3
  213. package/dist/{transcript-anchors-CiSi_UBn.js.map → transcript-anchors-DMjvyUjk.js.map} +1 -1
  214. package/dist/{transcript-reconcile-jbSBjLq2.js → transcript-reconcile-DpQO85wl.js} +3 -3
  215. package/dist/{transcript-reconcile-jbSBjLq2.js.map → transcript-reconcile-DpQO85wl.js.map} +1 -1
  216. package/dist/tui.d.ts +9 -9
  217. package/dist/tui.d.ts.map +1 -1
  218. package/dist/tui.js +289 -38
  219. package/dist/tui.js.map +1 -1
  220. package/dist/{types-bDQagoiL.d.ts → types-2HrgolQz.d.ts} +20 -8
  221. package/dist/types-2HrgolQz.d.ts.map +1 -0
  222. package/dist/types-4q1bFG53.js +122 -0
  223. package/dist/types-4q1bFG53.js.map +1 -0
  224. package/dist/{types-DnRd9_cA.d.ts → types-SXqk6hLD.d.ts} +3 -3
  225. package/dist/{types-DnRd9_cA.d.ts.map → types-SXqk6hLD.d.ts.map} +1 -1
  226. package/dist/types-myxheQkw.js.map +1 -1
  227. package/dist/types.d.ts +3 -3
  228. package/dist/{usage-summary-BfcXjiSM.js → usage-summary-BM7B3JdI.js} +2 -2
  229. package/dist/{usage-summary-BfcXjiSM.js.map → usage-summary-BM7B3JdI.js.map} +1 -1
  230. package/dist/{version-DqqqvHDP.js → version-szdW1Od9.js} +2 -2
  231. package/dist/{version-DqqqvHDP.js.map → version-szdW1Od9.js.map} +1 -1
  232. package/dist/{wait-task-BX4NcOOm.js → wait-task-BiI5UaZQ.js} +13 -3
  233. package/dist/wait-task-BiI5UaZQ.js.map +1 -0
  234. package/dist/{wire-CLm2rMK0.js → wire-C3l9jl9x.js} +2 -2
  235. package/dist/{wire-CLm2rMK0.js.map → wire-C3l9jl9x.js.map} +1 -1
  236. package/dist/{write-file-0E6TUJ9A.js → write-file-Dquu7MzB.js} +5 -3
  237. package/dist/write-file-Dquu7MzB.js.map +1 -0
  238. package/dist/{ws-DfGGYQ4W.js → ws-CEHi0CiB.js} +4 -4
  239. package/dist/{ws-DfGGYQ4W.js.map → ws-CEHi0CiB.js.map} +1 -1
  240. package/dist/{ws-client-CFMbhsNq.js → ws-client-t8XNWxcq.js} +2 -2
  241. package/dist/{ws-client-CFMbhsNq.js.map → ws-client-t8XNWxcq.js.map} +1 -1
  242. package/docs/CHAT.md +55 -0
  243. package/docs/CODEMODE.md +37 -0
  244. package/docs/EXTENSIONS.md +8 -6
  245. package/docs/RELEASE-NOTES-NEXT.md +48 -1
  246. package/docs/RELEASE.md +9 -1
  247. package/package.json +2 -3
  248. package/dist/agent-DJhpTS-B.js.map +0 -1
  249. package/dist/auto-update-restart-BFB7Ek87.js.map +0 -1
  250. package/dist/browser-BcoCCB_n.js.map +0 -1
  251. package/dist/contexts-C72b0mMa.js.map +0 -1
  252. package/dist/decisions-CXmK0nz9.js.map +0 -1
  253. package/dist/extension-mark-BqKdEZwF.js.map +0 -1
  254. package/dist/extensions-client-D8tEAcMw.d.ts.map +0 -1
  255. package/dist/host-BHMs9jsN.js.map +0 -1
  256. package/dist/host-wO_6GW0y.js.map +0 -1
  257. package/dist/index-CGbOWa7y.d.ts.map +0 -1
  258. package/dist/json-Dj-YATkZ.js.map +0 -1
  259. package/dist/media-type-BtA0cbfI.d.ts.map +0 -1
  260. package/dist/memory-DuVGrzbl.js.map +0 -1
  261. package/dist/nested-call-events-BYk8XvGX.js +0 -182
  262. package/dist/nested-call-events-BYk8XvGX.js.map +0 -1
  263. package/dist/nested-call-record-DlzPeTB7.js.map +0 -1
  264. package/dist/prelude-CVKoUzV8.js.map +0 -1
  265. package/dist/protocol-CEVqBO-q.d.ts.map +0 -1
  266. package/dist/providers-Z0TaBp1O.js.map +0 -1
  267. package/dist/registry-B9INJwJR.js.map +0 -1
  268. package/dist/repo-url-D4Dr8NfZ.js.map +0 -1
  269. package/dist/settings-catalog-4IU2i_jG.js.map +0 -1
  270. package/dist/source-Bl1Z501r.js.map +0 -1
  271. package/dist/sqlite-runtime-BZ2L4RaG.d.ts.map +0 -1
  272. package/dist/store-C6gI7FfZ.js.map +0 -1
  273. package/dist/todo-edits-CbukbjyV.js.map +0 -1
  274. package/dist/types-DTMEhKJO.js +0 -14
  275. package/dist/types-DTMEhKJO.js.map +0 -1
  276. package/dist/types-bDQagoiL.d.ts.map +0 -1
  277. package/dist/wait-task-BX4NcOOm.js.map +0 -1
  278. package/dist/write-file-0E6TUJ9A.js.map +0 -1
  279. package/scripts/eval.ts +0 -179
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-cli.js","names":[],"sources":["../src/daemon-cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `zidane-daemon` — the shared chat host behind every zidane window.\n *\n * Starts a unix-socket JSON-RPC server (see `src/ipc/daemon/server.ts`) so\n * multiple terminal / GUI windows attach to the same live agents and stay\n * in sync. Normally spawned on demand by a client (`connectOrSpawnDaemon`);\n * run it directly for a long-lived foreground daemon.\n *\n * REQUIRES BUN: the canonical session store is `bun:sqlite`. Users of the\n * published TUI don't need Bun installed — the compiled `zidane` binary\n * embeds it, and `zidane daemon` runs this same server in-process.\n *\n * Flags:\n * --socket <path> Socket path (default: $ZIDANE_DAEMON_SOCKET, else\n * <stateDir>/daemon/daemon.sock)\n * --idle-exit <ms> Exit after this long with zero connected peers\n * (default: 30 minutes; 0 disables)\n * --verbose Log peer / stream activity to stderr\n * --no-channels Don't start channels (also ZIDANE_NO_CHANNELS=1)\n */\n// channels-hook:start\nimport type { ChannelsHost } from './channels'\nimport type { HostDispatcher } from './ipc/daemon/dispatcher'\n// channels-hook:end\nimport { spawnSync } from 'node:child_process'\n// channels-hook:start\nimport { existsSync } from 'node:fs'\n// channels-hook:end\nimport { createRequire } from 'node:module'\n// channels-hook:start\nimport { join } from 'node:path'\n// channels-hook:end\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\n// channels-hook:start\nimport { resolveStorageDirs } from './chat/xdg'\n// channels-hook:end\nimport { defaultSocketPath } from './ipc/daemon/lifecycle'\nimport { startIpcDaemon } from './ipc/daemon/server'\nimport { fixPath } from './ipc/daemon/shell-path'\n// channels-hook:start\nimport { connectInProcess } from './ipc/memory'\n// channels-hook:end\n\nfunction appVersion(): string {\n try {\n const require = createRequire(import.meta.url)\n return (require('../package.json') as { version?: string }).version ?? 'dev'\n }\n catch {\n return 'dev'\n }\n}\n\nconst DEFAULT_IDLE_EXIT_MS = 30 * 60 * 1000\n\n// channels-hook:start\n/**\n * Channels on the laptop daemon (`docs/CHANNELS.md`): only when the storage\n * dir holds `channels.json` or `channels.config.ts` and the kill switch is\n * off, decided before src/channels is imported. Socket transports and owners\n * only: `hostChannels` refuses members and webhook transports at start (and\n * on reloads), logged whatever `--verbose` says. The runner's in-process\n * client is a peer, so the daemon never idles out while channels run.\n * `--no-channels` (or `ZIDANE_NO_CHANNELS=1`) keeps them off.\n */\nasync function startDaemonChannels(dispatcher: HostDispatcher, noChannels: boolean, verbose?: (line: string) => void): Promise<ChannelsHost | null> {\n // First: the runner unsets the secrets' variables, and every later load reads them from here.\n const env = { ...process.env }\n const dataDir = resolveStorageDirs().configDir\n // The kill switch and file names as src/channels/config/load.ts has them: deciding must not import it.\n if (noChannels || /^(?:1|true|yes|on)$/i.test(env.ZIDANE_NO_CHANNELS?.trim() ?? ''))\n return null\n if (!existsSync(join(dataDir, 'channels.json')) && !existsSync(join(dataDir, 'channels.config.ts')))\n return null\n const loud = (level: string) => (message: string, data?: Record<string, unknown>): void => {\n process.stderr.write(`zidane daemon: ${level}: ${message}${data && Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : ''}\\n`)\n }\n const quiet = (message: string, data?: Record<string, unknown>): void => verbose?.(`[channels] ${message}${data && Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : ''}`)\n try {\n const { hostChannels } = await import('./channels')\n return await hostChannels({\n flavor: 'daemon',\n connect: () => connectInProcess(dispatcher, { grant: 'approve', appVersion: 'zidane-channels' }),\n dataDir,\n env,\n log: { debug: () => undefined, info: quiet, warn: loud('warning'), error: loud('error') },\n })\n }\n catch (err) {\n loud('error')(`channels failed to start: ${err instanceof Error ? err.message : String(err)}`)\n return null\n }\n}\n\n// channels-hook:end\nasync function main(): Promise<void> {\n if (typeof (globalThis as { Bun?: unknown }).Bun === 'undefined') {\n /*\n * Node shebang so this guard actually RUNS on machines without bun\n * on PATH (a bun shebang dies inside `env` with a cryptic error\n * before any code executes). Re-exec under bun when it's available;\n * otherwise print the install guidance a bun shebang would preempt.\n */\n const script = process.argv[1]\n const reexec = script\n ? spawnSync('bun', [script, ...process.argv.slice(2)], { stdio: 'inherit' })\n : null\n if (reexec && reexec.error === undefined)\n process.exit(reexec.status ?? 0)\n\n process.stderr.write(\n 'zidane-daemon requires the Bun runtime (the session store uses bun:sqlite).\\n'\n + 'Install Bun (https://bun.sh), or use the compiled `zidane daemon` command which embeds it.\\n',\n )\n process.exit(1)\n }\n\n const { values } = parseArgs({\n args: process.argv.slice(2),\n options: {\n 'socket': { type: 'string' },\n 'idle-exit': { type: 'string' },\n 'verbose': { type: 'boolean', default: false },\n 'help': { type: 'boolean', default: false },\n // channels-hook:start\n 'no-channels': { type: 'boolean', default: false },\n // channels-hook:end\n },\n })\n\n if (values.help) {\n process.stdout.write(\n 'usage: zidane-daemon [--socket <path>] [--idle-exit <ms>] [--verbose] [--no-channels]\\n',\n )\n return\n }\n\n // Every agent shell command inherits this process's PATH; a daemon spawned\n // by a Dock-launched GUI would otherwise hand tools launchd's bare PATH.\n fixPath()\n\n const socketPath = values.socket ?? defaultSocketPath()\n const idleExitMs = values['idle-exit'] !== undefined ? Number(values['idle-exit']) : DEFAULT_IDLE_EXIT_MS\n const log = values.verbose ? (line: string) => process.stderr.write(`${line}\\n`) : undefined\n\n const daemon = await startIpcDaemon({\n socketPath,\n appVersion: appVersion(),\n // Once the channels connected (below).\n deferResume: true,\n ...(idleExitMs > 0\n ? {\n idleShutdownMs: idleExitMs,\n onIdle: () => {\n void daemon.close().finally(() => process.exit(0))\n },\n }\n : {}),\n ...(log ? { onLog: log } : {}),\n })\n\n process.stderr.write(`zidane daemon listening on ${daemon.socketPath} (pid ${process.pid})\\n`)\n let connected = Promise.resolve()\n // channels-hook:start\n const channels = await startDaemonChannels(daemon.dispatcher, values['no-channels'] === true, log)\n // A TS config reloads on SIGHUP (a JSON one also on save).\n if (channels) {\n process.on('SIGHUP', () => void channels.reload())\n connected = channels.ready()\n }\n // channels-hook:end\n // Once the channels connected: a run resumed before would stream to no conversation.\n void connected.then(daemon.dispatcher.resumeAfterRestart)\n\n const shutdown = (): void => {\n // channels-hook:start\n // The runner closes before the drain, so it starts no work the drain then aborts (its wait on presenter calls is bounded).\n if (channels) {\n void channels.close().catch(() => undefined).then(() => daemon.close()).finally(() => process.exit(0))\n return\n }\n // channels-hook:end\n void daemon.close().finally(() => process.exit(0))\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n process.stderr.write(`zidane-daemon: ${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"daemon-cli.js","names":[],"sources":["../src/daemon-cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `zidane-daemon` — the shared chat host behind every zidane window.\n *\n * Starts a unix-socket JSON-RPC server (see `src/ipc/daemon/server.ts`) so\n * multiple terminal / GUI windows attach to the same live agents and stay\n * in sync. Normally spawned on demand by a client (`connectOrSpawnDaemon`);\n * run it directly for a long-lived foreground daemon.\n *\n * REQUIRES BUN: the canonical session store is `bun:sqlite`. Users of the\n * published TUI don't need Bun installed — the compiled `zidane` binary\n * embeds it, and `zidane daemon` runs this same server in-process.\n *\n * Flags:\n * --socket <path> Socket path (default: $ZIDANE_DAEMON_SOCKET, else\n * <stateDir>/daemon/daemon.sock)\n * --idle-exit <ms> Exit after this long with zero connected peers\n * (default: 30 minutes; 0 disables)\n * --verbose Log peer / stream activity to stderr\n * --no-channels Don't start channels (also ZIDANE_NO_CHANNELS=1)\n */\n// channels-hook:start\nimport type { ChannelsHost } from './channels'\nimport type { HostDispatcher } from './ipc/daemon/dispatcher'\n// channels-hook:end\nimport { spawnSync } from 'node:child_process'\n// channels-hook:start\nimport { existsSync } from 'node:fs'\n// channels-hook:end\nimport { createRequire } from 'node:module'\n// channels-hook:start\nimport { join } from 'node:path'\n// channels-hook:end\nimport process from 'node:process'\nimport { parseArgs } from 'node:util'\n// channels-hook:start\nimport { resolveStorageDirs } from './chat/xdg'\nimport { cliStyle } from './cli-style'\n// channels-hook:end\nimport { defaultSocketPath } from './ipc/daemon/lifecycle'\nimport { startIpcDaemon } from './ipc/daemon/server'\nimport { fixPath } from './ipc/daemon/shell-path'\n// channels-hook:start\nimport { connectInProcess } from './ipc/memory'\n// channels-hook:end\n\nfunction appVersion(): string {\n try {\n const require = createRequire(import.meta.url)\n return (require('../package.json') as { version?: string }).version ?? 'dev'\n }\n catch {\n return 'dev'\n }\n}\n\nconst DEFAULT_IDLE_EXIT_MS = 30 * 60 * 1000\n\n// channels-hook:start\n/**\n * Channels on the laptop daemon (`docs/CHANNELS.md`): only when the storage\n * dir holds `channels.json` or `channels.config.ts` and the kill switch is\n * off, decided before src/channels is imported. Socket transports and owners\n * only: `hostChannels` refuses members and webhook transports at start (and\n * on reloads), logged whatever `--verbose` says. The runner's in-process\n * client is a peer, so the daemon never idles out while channels run.\n * `--no-channels` (or `ZIDANE_NO_CHANNELS=1`) keeps them off.\n */\nasync function startDaemonChannels(dispatcher: HostDispatcher, noChannels: boolean, verbose?: (line: string) => void): Promise<ChannelsHost | null> {\n // First: the runner unsets the secrets' variables, and every later load reads them from here.\n const env = { ...process.env }\n const dataDir = resolveStorageDirs().configDir\n // The kill switch and file names as src/channels/config/load.ts has them: deciding must not import it.\n if (noChannels || /^(?:1|true|yes|on)$/i.test(env.ZIDANE_NO_CHANNELS?.trim() ?? ''))\n return null\n if (!existsSync(join(dataDir, 'channels.json')) && !existsSync(join(dataDir, 'channels.config.ts')))\n return null\n const loud = (level: string) => (message: string, data?: Record<string, unknown>): void => {\n process.stderr.write(`zidane daemon: ${level}: ${message}${data && Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : ''}\\n`)\n }\n const quiet = (message: string, data?: Record<string, unknown>): void => verbose?.(`[channels] ${message}${data && Object.keys(data).length > 0 ? ` ${JSON.stringify(data)}` : ''}`)\n try {\n const { hostChannels } = await import('./channels')\n return await hostChannels({\n flavor: 'daemon',\n connect: () => connectInProcess(dispatcher, { grant: 'approve', appVersion: 'zidane-channels' }),\n dataDir,\n env,\n log: { debug: () => undefined, info: quiet, warn: loud('warning'), error: loud('error') },\n })\n }\n catch (err) {\n loud('error')(`channels failed to start: ${err instanceof Error ? err.message : String(err)}`)\n return null\n }\n}\n\n// channels-hook:end\nasync function main(): Promise<void> {\n if (typeof (globalThis as { Bun?: unknown }).Bun === 'undefined') {\n /*\n * Node shebang so this guard actually RUNS on machines without bun\n * on PATH (a bun shebang dies inside `env` with a cryptic error\n * before any code executes). Re-exec under bun when it's available;\n * otherwise print the install guidance a bun shebang would preempt.\n */\n const script = process.argv[1]\n const reexec = script\n ? spawnSync('bun', [script, ...process.argv.slice(2)], { stdio: 'inherit' })\n : null\n if (reexec && reexec.error === undefined)\n process.exit(reexec.status ?? 0)\n\n process.stderr.write(\n 'zidane-daemon requires the Bun runtime (the session store uses bun:sqlite).\\n'\n + 'Install Bun (https://bun.sh), or use the compiled `zidane daemon` command which embeds it.\\n',\n )\n process.exit(1)\n }\n\n const { values } = parseArgs({\n args: process.argv.slice(2),\n options: {\n 'socket': { type: 'string' },\n 'idle-exit': { type: 'string' },\n 'verbose': { type: 'boolean', default: false },\n 'help': { type: 'boolean', default: false },\n // channels-hook:start\n 'no-channels': { type: 'boolean', default: false },\n // channels-hook:end\n },\n })\n\n if (values.help) {\n process.stdout.write(\n 'usage: zidane-daemon [--socket <path>] [--idle-exit <ms>] [--verbose] [--no-channels]\\n',\n )\n return\n }\n\n // Every agent shell command inherits this process's PATH; a daemon spawned\n // by a Dock-launched GUI would otherwise hand tools launchd's bare PATH.\n fixPath()\n\n const socketPath = values.socket ?? defaultSocketPath()\n const idleExitMs = values['idle-exit'] !== undefined ? Number(values['idle-exit']) : DEFAULT_IDLE_EXIT_MS\n const log = values.verbose ? (line: string) => process.stderr.write(`${line}\\n`) : undefined\n\n const daemon = await startIpcDaemon({\n socketPath,\n appVersion: appVersion(),\n // Once the channels connected (below).\n deferResume: true,\n ...(idleExitMs > 0\n ? {\n idleShutdownMs: idleExitMs,\n onIdle: () => {\n void daemon.close().finally(() => process.exit(0))\n },\n }\n : {}),\n ...(log ? { onLog: log } : {}),\n })\n\n process.stderr.write(`${cliStyle(process.stderr).step('ok', `zidane daemon listening on ${daemon.socketPath}`, `(pid ${process.pid})`)}\\n`)\n let connected = Promise.resolve()\n // channels-hook:start\n const channels = await startDaemonChannels(daemon.dispatcher, values['no-channels'] === true, log)\n // A TS config reloads on SIGHUP (a JSON one also on save).\n if (channels) {\n process.on('SIGHUP', () => void channels.reload())\n connected = channels.ready()\n }\n // channels-hook:end\n // Once the channels connected: a run resumed before would stream to no conversation.\n void connected.then(daemon.dispatcher.resumeAfterRestart)\n\n const shutdown = (): void => {\n // channels-hook:start\n // The runner closes before the drain, so it starts no work the drain then aborts (its wait on presenter calls is bounded).\n if (channels) {\n void channels.close().catch(() => undefined).then(() => daemon.close()).finally(() => process.exit(0))\n return\n }\n // channels-hook:end\n void daemon.close().finally(() => process.exit(0))\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n process.stderr.write(`zidane-daemon: ${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
@@ -91,6 +91,7 @@ const HOOK_EVENT_SET = /* @__PURE__ */ new Set([
91
91
  "tool:after",
92
92
  "tool:error",
93
93
  "tool:cancelled",
94
+ "tool:progress",
94
95
  "tool:transform",
95
96
  "tool:unknown",
96
97
  "tool-state:commit-failed",
@@ -125,6 +126,7 @@ const HOOK_EVENT_SET = /* @__PURE__ */ new Set([
125
126
  "child:validation:reject",
126
127
  "child:repeat-guard:exceeded",
127
128
  "child:tool:cancelled",
129
+ "child:tool:progress",
128
130
  "child:background:start",
129
131
  "child:background:exit",
130
132
  "child:background:reassign",
@@ -426,4 +428,4 @@ async function journaledDecide(journal, decisions, request, signal) {
426
428
  //#endregion
427
429
  export { readonlyView as a, isKnownHookEvent as i, createDecider as n, unwrapReadonly as o, isDecisionHookEvent as r, DEFAULT_DECISION_TIMEOUT_MS as t };
428
430
 
429
- //# sourceMappingURL=decisions-CXmK0nz9.js.map
431
+ //# sourceMappingURL=decisions-hsw5XS2g.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisions-hsw5XS2g.js","names":[],"sources":["../src/extensions/hooks/readonly.ts","../src/agent-hooks.ts","../src/decisions/index.ts"],"sourcesContent":["/**\n * Shallow read-only views over hook payloads. Observers and interceptors\n * get a view whose own properties cannot be set or deleted; nested values\n * are returned as they are, so class instances (sessions, providers) keep\n * working. Only plain objects and arrays are wrapped.\n */\n\nconst viewOf = new WeakMap<object, object>()\nconst targetOf = new WeakMap<object, object>()\n\nfunction refuse(prop: string | symbol): never {\n throw new TypeError(`Hook payloads are read-only: cannot change \"${String(prop)}\". Return a new value from an interceptor instead.`)\n}\n\nconst HANDLER: ProxyHandler<object> = {\n set: (_target, prop) => refuse(prop),\n deleteProperty: (_target, prop) => refuse(prop),\n defineProperty: (_target, prop) => refuse(prop),\n setPrototypeOf: () => refuse('__proto__'),\n}\n\nfunction isPlain(value: object): boolean {\n if (Array.isArray(value))\n return true\n const proto = Object.getPrototypeOf(value) as unknown\n return proto === Object.prototype || proto === null\n}\n\n/**\n * Returns a shallow read-only view of `value`. Primitives, class instances\n * and existing views come back unchanged. The same target always yields the\n * same view.\n *\n * @param value - The payload to protect.\n * @returns A proxy that throws on writes to its own properties, or `value`.\n */\nexport function readonlyView<T>(value: T): T {\n if (value === null || typeof value !== 'object' || targetOf.has(value) || !isPlain(value))\n return value\n let view = viewOf.get(value)\n if (!view) {\n view = new Proxy(value, HANDLER)\n viewOf.set(value, view)\n targetOf.set(view, value)\n }\n return view as T\n}\n\n/**\n * Returns the object behind a view made by {@link readonlyView}, or `value`\n * when it is not a view. Interceptors may return the `current` view they\n * were given; the chain unwraps it before handing it back to the caller.\n *\n * @param value - A view or any value.\n * @returns The writable target.\n */\nexport function unwrapReadonly<T>(value: T): T {\n if (value === null || typeof value !== 'object')\n return value\n return (targetOf.get(value) as T | undefined) ?? value\n}\n","/**\n * The agent hook surface: the `AgentHooks` event map, its runtime name\n * registry (kept exhaustive at compile time), and the `AgentHookMap`\n * registration shape.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { CacheBreakInfo, CacheDriftInfo } from './cache-telemetry'\nimport type { ExecutionHandle, TaskExitInfo } from './contexts'\nimport type { AgentDecide, DecisionEndHookContext, DecisionStartHookContext } from './decisions/types'\nimport type { Preset } from './presets'\nimport type { StreamOptions, ToolResult } from './providers'\nimport type { Session, SessionArtifact } from './session'\nimport type { PairingRepair } from './session/messages'\nimport type { ActivationVia, DeactivationReason } from './skills/activation'\nimport type { SkillConfig } from './skills/types'\nimport type { ToolStateOp } from './tool-state'\nimport type { AgentBehavior, AgentRunOptions, AgentStats, ChildRunStats, ChildSpawnHookContext, ChildSpawnIdentity, CompactEndHookContext, CompactProgressHookContext, CompactStartHookContext, HistoryRepairHookContext, InputTransformationsHookContext, JsonValue, McpToolHookContext, McpToolSchema, OAuthRefreshHookContext, ResolveContentRefBlock, SessionEndStatus, SessionHookContext, SessionMessage, SessionTurn, SpawnHookContext, SpawnQueuedContext, StreamHookContext, StreamProgressHookContext, ToolHookContext, ToolOutcome, ToolProgress, ToolResultContent, ToolResultMetadata, TurnUsage } from './types'\n\n// ---------------------------------------------------------------------------\n// Hook definitions\n// ---------------------------------------------------------------------------\n\n/** What `tool:gate` carries for a call of a resumed checkpoint run. See `AgentHooks['tool:gate']`. */\nexport interface ToolGateResumeFields {\n /** The call passed the gate in a process that died: restore, don't ask. Its `result` is ignored, and its `block` too unless the call's body runs again (a safe call cut off, or a call with no step of its own). */\n resumed?: true\n /**\n * Per-call state the gate's handlers keep with the call's step record, by\n * handler key: write it on a live call, read it back on a `resumed` one.\n * Set by the loop on every call of a run that records steps, except the\n * calls a replay-mode batch joins. Only a resumed checkpoint run reads it back.\n */\n resumeState?: Record<string, JsonValue>\n}\n\n/** Event map for the agent hook bus. Each key is a hook name and each value is the handler signature. Register handlers with `agent.hooks.hook(name, fn)` or the `hooks` option of {@link createAgent}. */\nexport interface AgentHooks {\n // System\n /**\n * Fires once per run, before the first turn, only when `AgentRunOptions.system` is set. `ctx.system`\n * is that run-level string. Observational: the agent does not read `ctx.system` back, so use\n * `system:transform` to rewrite the system prompt.\n */\n 'system:before': (ctx: { system: string }) => void\n\n // Agent lifecycle (start)\n /**\n * Fires once per `agent.run()` invocation, right after `session:start`\n * (when a session is bound) and before the first `turn:before`. Symmetric\n * with {@link AgentHooks['agent:done']}.\n *\n * `tracingContext` carries opaque parent trace metadata propagated from\n * the spawning agent (typically a W3C `{ traceparent, tracestate }`\n * carrier injected by the parent's tracer on `spawn:before`). Tracers\n * read it on the child run to continue the parent trace; absent on\n * top-level runs.\n */\n 'agent:start': (ctx: {\n runId: string\n parentRunId?: string\n depth: number\n agentName?: string\n /**\n * Provider identity for this run, lower-cased canonical form\n * (`anthropic`, `openai`, `openrouter`, `cerebras`, …). Exposed so\n * observability adapters can stamp `gen_ai.system` (Sentry / OTel\n * Gen AI semantic-convention identifier) on every child span of\n * this run without needing a back-channel to the agent. Absent\n * when the provider declined to identify itself.\n */\n providerName?: string\n startedAt: number\n tracingContext?: Readonly<Record<string, string>>\n }) => void\n\n // Turn lifecycle\n /**\n * Fires immediately before each turn's `provider.stream()` call, after\n * `context:transform` + pairing repair + `system:transform`. The\n * `options` object IS mutable as a last-mile escape hatch (per-turn\n * reminders, dynamic tags, audit wrapping), but prefer\n * {@link AgentHooks['context:transform']} for any mutation that touches\n * `messages` — it runs upstream of the defensive pairing pass and stays\n * cheaper. Loop re-runs `ensureToolResultPairing` + `ensureEndsWithUserMessage`\n * after this hook so a `messages` mutation here can't leak a\n * `tool_result must be preceded by a tool_call` 400 to the wire, but you\n * pay one extra walk for the privilege.\n */\n 'turn:before': (ctx: { turn: number, turnId: string, options: StreamOptions }) => void\n /**\n * Fires when a turn read back materially less cached prefix than the\n * previous turn established — a prompt-cache break. Emitted by\n * `installCacheBreakLogger` (zidane's cache-telemetry module), so like\n * `compact:*` it only fires when that subsystem is installed on this\n * agent's hook bus; the loop itself never emits it.\n *\n * The payload names the request dimensions that changed (`changed`) with\n * human-readable diffs (`details`). An empty `changed` means the request\n * was byte-stable at this wire and the provider still dropped the prefix —\n * TTL expiry, provider eviction, or (behind a format-casting gateway)\n * churn introduced by the cast itself; see {@link CacheBreakInfo} for how\n * to tell them apart. Hosts surface this as UI (TUI footer chip), metrics,\n * or alerts; the text `log` line remains available for grep-based\n * workflows and, unlike this hook, stays silent on unattributable breaks.\n */\n 'cache:break': (info: CacheBreakInfo) => void | Promise<void>\n /**\n * Fires at `turn:before` when the outgoing request DIVERGES from the\n * previous completed turn's — the predictive counterpart of `'cache:break'`.\n * Emitted by `installCacheBreakLogger` from the request bytes alone, so it\n * works on providers that report no cache usage and on format-casting\n * gateways where this wire is the last observable point before the cast.\n *\n * Drift + healthy `cacheRead` afterwards = the provider absorbed the change;\n * drift + a `'cache:break'` on the same turn = the break's attribution.\n * Intentional rewrites (compaction cuts, stale-read stubs) drift by design —\n * `divergedAt` tells them apart; see `CacheDriftInfo`.\n *\n * Delivery is fire-and-forget: `turn:before` rides the loop's PROPAGATING\n * hook timeout, so the emitter does not await consumers. Synchronous\n * handlers complete before the request goes out; async handlers detach and\n * must not assume the turn is still in flight; errors route to the\n * installer's `onEmitError`, never into the run.\n */\n 'cache:drift': (info: CacheDriftInfo) => void | Promise<void>\n /**\n * Fires after each assistant turn (before its tool-result follow-up\n * dispatches; the loop iterates back to a fresh `turn:before` once the\n * tool results are produced).\n *\n * `toolCounts.turn` — calls **emitted** by the model in this assistant\n * turn, keyed by canonical tool name. Reflects what the model asked for,\n * regardless of downstream gate outcome. Most useful for spotting per-turn\n * spikes (\"the model called todowrite 4 times in one turn\").\n *\n * `toolCounts.run` — cumulative running counter of **dispatched** calls\n * scoped to this `runId`, captured at fire time. Excludes calls that were\n * `block`ed by `tool:gate` handlers. Includes calls short-circuited via\n * `tool:gate` `result` substitution (the model still asked, the framework\n * just answered without the tool running). Resumed sessions start a fresh\n * run with empty counts.\n *\n * Both fields are frozen snapshots; mutate-safe.\n *\n * `aborted` — runtime emissions always include this boolean; it is optional\n * on the type only so tests/adapters that manually synthesize `turn:after`\n * payloads do not break (treat absent as `false`). `true` when the run's\n * `AbortController` was already tripped at the moment this `turn:after`\n * fired. This is the case to branch on instead of `usage.finishReason` when\n * emulating Stop-hook semantics: an aborted turn can still carry\n * `finishReason: 'stop'` (e.g. a final text turn the user cancelled\n * mid-stream), so `finishReason` alone cannot distinguish \"model finished\"\n * from \"we killed it\".\n *\n * Covers the streaming-abort path (cancel landed while the turn was\n * streaming) and any abort that landed before this fired. NOTE: a\n * `tool:gate` / `repeat-guard` abort trips DURING the tool batch, which runs\n * after this `turn:after` — so the offending turn reports `aborted: false`\n * here (its `finishReason` is `'tool-calls'`, never `'stop'`, so a\n * `'stop'`-keyed consumer won't misfire). That abort surfaces on the\n * subsequent `agent:abort`. For the post-completion window (host queues a\n * follow-up after a clean `turn:after`, then the run is aborted), rely on\n * {@link Agent.steer}/{@link Agent.followUp} returning `false` once aborted.\n * `false` on every normal turn boundary.\n */\n 'turn:after': (ctx: {\n turn: number\n turnId: string\n usage: TurnUsage\n message: SessionTurn\n aborted?: boolean\n toolCounts: {\n turn: Readonly<Record<string, number>>\n run: Readonly<Record<string, number>>\n }\n /**\n * Run-cumulative token + cost rollup **including this turn**. Computed\n * locally in the loop from `priorUsage + usage`. Lets observability\n * consumers stamp running totals onto turn spans / metrics without\n * re-accumulating across handlers.\n *\n * `turns` is the number of completed parent-loop turns inclusive of this\n * one (1-indexed for the first turn). `cost` is summed from per-turn\n * `TurnUsage.cost` reports; absent when nothing reported a non-zero cost.\n *\n * Children's spend is NOT folded in here — `turn:after` fires on parent\n * turns; child rollup lands on `spawn:complete` / final `agent:done`.\n */\n cumulativeUsage: Readonly<{\n input: number\n output: number\n cacheRead: number\n cacheCreation: number\n cost?: number\n turns: number\n }>\n }) => void\n\n /**\n * Fires after a tool-results user turn is pushed onto the conversation,\n * before any byte-budget enforcement or follow-up steering. Symmetric\n * with `turn:after` but for the user-role tool_result turn that closes the\n * round-trip.\n *\n * Why it exists: persistence layers must write tool_use/tool_result turn\n * pairs together — if only the assistant turn (carrying tool_use blocks)\n * is durable, a process death between turns leaves the DB with an orphan\n * tool_use that Anthropic rejects on resume. Subscribe here to persist\n * the tool-results turn before the loop continues.\n */\n 'tool-results:after': (ctx: {\n turn: number\n turnId: string\n message: SessionTurn\n /** Frozen list of tool results that were packed into `message`. */\n results: readonly ToolResult[]\n }) => void\n /**\n * Fires when a durable run resumes a tool batch a previous process left\n * unfinished, before any call of it runs again. `done` calls were rebuilt\n * from their step records (show them finished); `resuming` calls run\n * again or are reported interrupted. Optional for hosts: one that ignores\n * it shows the run as running and fills in the calls when the batch ends.\n *\n * Checkpoint runtimes only: in replay mode the loop cannot tell which calls\n * are done before it issues them, and the hook would fire on every replay.\n */\n 'tool-batch:resumed': (ctx: {\n runId: string\n turn: number\n turnId: string\n /** Call ids answered from their records, in call order. */\n done: readonly string[]\n /** Call ids still to settle, in call order. */\n resuming: readonly string[]\n }) => void\n /**\n * `Agent.resume` refused a run whose stored step records are of another\n * loop turn than the one its saved turns resume at (a history rewrite):\n * asking for keys of the wrong turn would miss a pending unsafe call and\n * run it again. Also fires when the run's saved turns leave a tool call\n * unanswered before their last turn (they were rewritten after the loop\n * saved them). The run is no longer resumable: its records are dropped,\n * its dangling tool calls are reported interrupted (a spawned child's are\n * left to the next top-level run), and `resume()` resolves `null`.\n */\n 'resume:mismatch': (ctx: {\n sessionId: string\n runId: string\n /** The loop turn the saved turns resume at. */\n resumeTurn: number\n /** The loop turns the stored step records are of. */\n storedTurns: readonly number[]\n /** Why the run cannot resume, in words. */\n reason: string\n }) => void\n\n // Streaming\n /**\n * Fires immediately before the loop calls `provider.stream()`. Pairs with\n * `stream:end` / `stream:error`; lets observers timestamp the moment the\n * request leaves the client to compute wait-vs-generate split and TTFT\n * relative to provider dispatch (not run start).\n *\n * `startedAt` is `Date.now()` at fire time. Observational — handlers\n * cannot mutate the in-flight `StreamOptions` (use `turn:before` /\n * `context:transform` / `system:transform` for that).\n */\n 'stream:start': (ctx: StreamHookContext & { startedAt: number }) => void\n /** Fires for each text delta of the model response. `delta` is the new chunk and `text` the accumulated text. */\n 'stream:text': (ctx: StreamHookContext & { delta: string, text: string }) => void\n /** Fires when the model's text stream for the turn ends, with the full text. */\n 'stream:end': (ctx: StreamHookContext & { text: string }) => void\n /** Fires for each thinking delta. `delta` is the new chunk and `thinking` the accumulated thinking text. */\n 'stream:thinking': (ctx: StreamHookContext & { delta: string, thinking: string }) => void\n /**\n * Fires (throttled, ~4/s) while the parent turn streams and once more,\n * unthrottled, right before `stream:end`. Carries the tokens streamed so far\n * (provider-counted when the wire reports mid-stream usage, else chars ÷ 4)\n * and a running USD estimate for the turn. Hosts paint live counters from\n * it; `turn:after` settles the figure. Not bubbled for subagents — their\n * spend lands at `child:turn:after`. Observational and best-effort.\n */\n 'stream:progress': (ctx: StreamProgressHookContext) => void\n /**\n * Fires mid-stream when a provider that executes a tool server-side\n * (currently: Anthropic's `web_search_20250305`) closes the\n * server-tool-invocation content block. Symmetric with `tool:before` for\n * function tools — surface the call into the transcript at the moment the\n * model issued it, without waiting for the assistant message to finalize.\n *\n * Shape is intentionally generic so future server tools reuse the same\n * hook. `id` is the block's tool-use id (echoed back to the provider on\n * follow-up turns); pair with `stream:server_tool_result` via that id.\n *\n * Observational — handlers cannot block or substitute, since the call\n * has already executed server-side by the time we see it.\n */\n 'stream:server_tool_use': (ctx: StreamHookContext & {\n id: string\n name: string\n input: Record<string, unknown>\n }) => void\n /**\n * Pairs with {@link AgentHooks['stream:server_tool_use']} — fires when the\n * provider closes the matching server-tool-result block. `content` is the\n * raw block payload (either an error object or an array of result entries\n * carrying `encrypted_content` / `encrypted_index`); consumers format it\n * for display.\n */\n 'stream:server_tool_result': (ctx: StreamHookContext & {\n toolUseId: string\n toolName: string\n content: unknown\n }) => void\n /**\n * Fires when the provider's stream rejects BEFORE `stream:end` — provider\n * errors, network blips, malformed `tools` payloads (400 invalid_request_error).\n *\n * `err` is the original thrown value, not the typed `AgentProviderError`\n * the loop subsequently constructs; subscribe here to capture the native\n * SDK exception (status codes, request ids) for telemetry without having\n * to walk `cause` chains. Observational — the loop still wraps + throws\n * after this hook resolves, so handlers cannot suppress the failure.\n *\n * Best-effort convenience fields extracted from common SDK shapes\n * (Anthropic, OpenAI, OpenRouter, Cerebras) when present on `err`:\n * - `statusCode` — HTTP status (e.g. 400, 429, 500, 502, 529)\n * - `requestId` — provider request id for grep-the-logs correlation\n *\n * Both absent when the SDK doesn't expose them (network/early rejects,\n * stream-internal protocol errors). Walk `err.cause` chains yourself for\n * deeper diagnostics.\n *\n * Does NOT fire on user-initiated aborts; those land on `agent:abort`\n * instead. Use `err instanceof Error && err.name === 'AbortError'` to\n * distinguish if you also subscribe here for raw error logging.\n */\n 'stream:error': (ctx: StreamHookContext & {\n err: unknown\n statusCode?: number\n requestId?: string\n }) => void\n /**\n * Fires when the loop catches a `retryable` provider error and is about\n * to sleep before re-issuing `provider.stream()`. Does NOT fire on the\n * terminal failure (the attempt the loop gives up on) — that lands on\n * `stream:error` instead.\n *\n * Use this hook to surface \"Anthropic overloaded — retrying in Ns\" in\n * the TUI, count retries in observability, or short-circuit further\n * retry logic from an outer harness.\n *\n * - `attempt` — 1-indexed number of the attempt that just failed.\n * - `nextAttempt` — `attempt + 1`, the attempt that will run after the sleep.\n * - `delayMs` — the wait the loop is about to take. Already accounts for\n * any `retry-after` / `retry-after-ms` header the server returned and\n * the configured `maxDelayMs` cap.\n * - `err` — the raw thrown value (same as `stream:error.err`).\n * - `statusCode` / `requestId` — extracted via the same helper as\n * `stream:error` when available.\n *\n * Observational — handlers cannot cancel the retry. To disable retry,\n * set `behavior.retry.maxAttempts: 1`.\n */\n 'stream:retry': (ctx: StreamHookContext & {\n attempt: number\n nextAttempt: number\n /** The run's retry budget (`behavior.retry.maxAttempts`) — lets UIs render `retry n/max`. */\n maxAttempts: number\n delayMs: number\n err: unknown\n statusCode?: number\n requestId?: string\n /**\n * `true` when the failed attempt streamed reasoning (and nothing else)\n * before it failed. That reasoning is dropped: it is not persisted, and\n * the next attempt's `stream:thinking` `thinking` snapshots start\n * empty. Hosts that showed it live should clear it for this turn.\n */\n discardedThinking?: true\n }) => void\n /**\n * Fires when a stream fails terminally on the active provider+model target\n * and the loop advances to the next entry of the run's fallback chain\n * (`AgentRunOptions.model` array / multi-provider fan-out) instead of\n * surfacing the error. Fires once per advancement; the terminal failure of\n * the LAST target still lands on `stream:error` as usual.\n *\n * Fallback is run-sticky: after this fires, the rest of the run stays on\n * `to` (no ping-pong back to the primary); the next `run()` starts from the\n * primary target again.\n *\n * - `from` / `to` — provider name + model id of the exhausted and newly\n * active targets.\n * - `targetIndex` — 0-based index of `to` in the resolved target list.\n * - `err` — the classified error that exhausted `from` (an\n * `AgentProviderError` whose `limit` says when it was a rate limit, spent\n * quota or overload); the raw provider error is its `cause`.\n * - `discardedThinking` — `true` when the failed attempt streamed\n * reasoning before it failed; same meaning as on `stream:retry`.\n *\n * Observational — handlers cannot cancel the fallback. To disable fallback,\n * configure a single provider+model target.\n *\n * Attribution note: `agent:start`'s `providerName` comes from the PRIMARY\n * target and is not updated when a fallback advances — this hook (and\n * per-turn `TurnUsage.modelId`) is the source of\n * truth for which target actually served a given stretch of the run.\n */\n 'model:fallback': (ctx: StreamHookContext & {\n from: { provider: string, model: string }\n to: { provider: string, model: string }\n targetIndex: number\n err: Error\n /** See {@link AgentHooks['stream:retry']}'s `discardedThinking`. */\n discardedThinking?: true\n }) => void\n /**\n * Fires when the loop catches a `context_exceeded` provider error and, before\n * giving up, performs an emergency in-loop compaction (aggressive tail\n * elision of older `tool_result` bodies) and re-issues `provider.stream()`.\n * Does NOT fire when compaction can't shrink the request any further — that\n * lands on `stream:error` (the terminal failure).\n *\n * Runs independently of `behavior.compactStrategy`: the recovery calls the\n * tail compactor directly, so it protects runs that never opted into\n * client-side compaction. It can only shrink tool-result-heavy histories — a\n * request whose mandatory text alone overflows still surfaces the error.\n *\n * - `attempt` — 1-indexed recovery pass (each pass keeps fewer trailing turns\n * intact and elides more; bounded).\n * - `err` — the raw `context_exceeded` error that triggered recovery.\n *\n * Observational — handlers cannot cancel the recovery.\n */\n 'context:overflow': (ctx: StreamHookContext & {\n attempt: number\n err: unknown\n }) => void\n /**\n * Fires when loop-native auto-compaction (`behavior.autoCompact`) crosses\n * its threshold, or `agent.compact()` starts (`ctx.reason: 'manual'`), and\n * the summary call is about to run, BEFORE the provider round-trip.\n * Distinct from `context:overflow` (the reactive, wire-only\n * `context_exceeded` net) — this is the heavy compaction that summarizes\n * older turns and mutates the session.\n *\n * Use it to surface a \"Compacting…\" banner / footer state. Observational\n * — handlers cannot cancel the compaction. Always paired with exactly one\n * `compact:end`.\n */\n 'compact:start': (ctx: CompactStartHookContext) => void\n /**\n * Fires (throttled) while the compaction summary streams — carries a\n * rough cumulative output-token estimate (`estOutputTokens`, chars ÷ 4)\n * so hosts can paint live progress next to the \"Compacting…\" state.\n * Zero or more per `compact:start`; best-effort and observational.\n */\n 'compact:progress': (ctx: CompactProgressHookContext) => void\n /**\n * Fires exactly once after every `compact:start`, on success and failure\n * (`ctx.status`). On success it carries the replaced/dropped/restored\n * counts and the post-compact `effectiveTokens` estimate so hosts can drop\n * their context indicator immediately. On failure it carries `error`; an\n * auto-compaction's run continues with the un-compacted history (the\n * reactive overflow net still applies) and the circuit breaker counts the\n * failure, while `agent.compact()` rejects with the error (or resolves\n * `compacted: false` when it was aborted).\n */\n 'compact:end': (ctx: CompactEndHookContext) => void\n /**\n * Fires after a provider stream settles when the loop scrubbed\n * provider-rejected reasoning state from canonical history (e.g.\n * Anthropic signature-400 poisoning). In-memory turns and the bound\n * session are already repaired; hosts mirroring turns into an external\n * durable store must heal it using `ctx.turnIds` or the next reload\n * re-poisons the run. Observational. See {@link HistoryRepairHookContext}.\n */\n 'history:repair': (ctx: HistoryRepairHookContext) => void\n /**\n * Fires when the provider dropped replayed thinking blocks server-side\n * before sampling (Anthropic preserved-thinking binding, reported via\n * `input_transformations`). Nothing was mutated on our side — contrast\n * `'history:repair'`. A `prefix_binding_mismatch` entry means this host\n * rewrote system / tools / an earlier message since the block was\n * produced: the same edit `'cache:break'` attributes, now also costing the\n * model its reasoning. Observational. See {@link InputTransformationsHookContext}.\n */\n 'provider:input-transformations': (ctx: InputTransformationsHookContext) => void\n /** Fires when the provider reports a diagnostic event for a turn. */\n 'provider:diagnostic': (ctx: import('./providers/diagnostic').ProviderDiagnostic & { runId: string, turnId: string }) => void\n /** Fires when the provider refreshes its OAuth credentials during a run. */\n 'oauth:refresh': (ctx: OAuthRefreshHookContext) => void\n\n /**\n * Resolves persisted content refs into provider-bound payload bytes.\n *\n * Fires only when wire assembly sees a block carrying `ref`. Handlers set\n * `ctx.data` to the resolved payload. The bytes are wire-only and are never\n * written back to `session.turns`.\n */\n 'content-ref:resolve': (ctx: {\n block: ResolveContentRefBlock\n data?: string\n childId?: string\n depth?: number\n }) => void\n\n // Tool execution\n /**\n * Fires before validation, `tool:before`, and `execute`. Two ways to\n * intercept:\n *\n * - Set `block = true` (with a `reason`) to refuse the call. The model\n * sees a `tool_result` block with `content` = `Blocked: <reason>` and\n * `is_error: true`. `tool:before` / `tool:after` do **not** fire;\n * `tool:dispatched` does (with `outcome: 'gate-block'`) so trace /\n * audit consumers see the refusal. The exact wire payload is:\n *\n * ```jsonc\n * {\n * \"type\": \"tool_result\",\n * \"tool_use_id\": \"<call id>\",\n * \"content\": \"Blocked: <reason>\", // verbatim, no wrapping XML\n * \"is_error\": true\n * }\n * ```\n *\n * The `Blocked: ` prefix is fixed (single space, no colon variations);\n * the `<reason>` is whatever string the gate handler assigned to\n * `ctx.reason` (defaults to `'Tool execution was blocked'` if no\n * handler set it). Pinned by `test/per-run-hooks.test.ts` so SDK\n * consumers can safely string-match for telemetry / eval scoring.\n * - Set `result` to substitute a successful tool_result and skip\n * execution. The model sees the substitute as a normal tool_result;\n * `tool:before` does not fire, but `tool:after` and `tool:transform`\n * do — so byte budgets, telemetry, and post-mutation hooks see the\n * substitute. Useful for cache hits, dedup, idempotency guards,\n * plan-mode synthetic acks.\n *\n * If multiple handlers along the chain set both `block` and `result`,\n * `block` wins — refusal beats substitution, so a policy gate\n * (skills allow-list, custom security) can always override an upstream\n * consumer's cache substitute. Mirrors the writable-`result` shape on\n * `tool:unknown` and `tool:error` so consumers learn one pattern.\n *\n * `runToolCounts` — frozen pre-call snapshot of per-tool dispatched\n * counts in this run. Use it to self-throttle, drive observability, or\n * implement budget guards. Counts every call that passed gate, including\n * dedup substitutes; excludes `block`ed calls.\n *\n * Concurrent fleets see the same pre-batch snapshot on every member's\n * `tool:gate`; the built-in budget / dedup middleware reserves per-call\n * atomically so `behavior.toolBudgets` enforces correctly even when\n * multiple `tool:gate` callbacks fire in the same microtask.\n *\n * Gate a tool call before it runs. Handlers share ONE mutable `ctx` and run\n * sequentially in registration order (the order they were `hook()`-ed). The\n * framework reads the final state once all handlers (including async ones,\n * which are awaited) have run. Built-in behavior gates install during run\n * setup and self-defer when another gate already set `block` / `result`.\n *\n * Precedence across the resolved state:\n * - `block` beats `result`: if any handler left `block === true`, the call\n * is refused (`Blocked: <reason>`, `isError: true`) and any `result`\n * substitute is dropped. Security/refusal beats convenience/substitution.\n * - Among handlers, it is effectively last-writer-wins per field (each just\n * mutates the shared `ctx`). Cooperative built-ins self-defer — e.g.\n * `repeat-guard` early-returns when `ctx.block || ctx.result` is already\n * set, so it never double-penalizes a call another guard already broke.\n * Host handlers composing with the built-ins should do the same\n * (check `ctx.block`/`ctx.result` before acting) to keep precedence\n * predictable.\n *\n * Set `block = true` (+ `reason`) to refuse, or assign `result` to\n * short-circuit with a synthetic successful tool_result (the body never\n * runs; `tool:after` still fires). Mutating `ctx.input` rewrites the args\n * the body and downstream hooks see. `metadata` rides the refused\n * tool_result (read only when the call is blocked).\n *\n * Resumed calls (`resumed`): a checkpoint run resumed after a process\n * death dispatches again the calls whose step was recorded, which passed\n * the gate in the process that died. Their gate runs again so handlers\n * restore what they keep per call, without asking anyone: the framework\n * ignores `result` for them, and `block` too unless the call's body runs\n * again. What a handler writes to\n * `resumeState` (JSON, under a key of its own) is stored with the call's\n * step record and comes back on a resumed call.\n */\n 'tool:gate': (ctx: ToolHookContext & ToolGateResumeFields & {\n block: boolean\n reason: string\n result?: string | ToolResultContent[]\n metadata: ToolResultMetadata\n runToolCounts: Readonly<Record<string, number>>\n }) => void\n /** Fires before a tool body runs. Mutate `ctx.input` to rewrite the arguments the body sees. */\n 'tool:before': (ctx: ToolHookContext & {\n coercions?: readonly string[]\n runToolCounts: Readonly<Record<string, number>>\n /**\n * Pre-write file snapshot — set on a subagent's `tool:before` by the\n * enricher in `spawn-child.ts` when the call is `write_file`, `edit` or\n * `multi_edit` and the target path is readable. Forwarded\n * through to the parent's `child:tool:before` bubble so hosts can\n * render a true `old → new` diff. Absent for every other tool, and\n * for `write_file` on a fresh-create.\n */\n priorContent?: string\n /**\n * `true` when the call's name has no matching registered tool — the\n * model invented it (hallucination) or referenced a tool whose\n * registration is gone (an MCP server dropped, an alias removed).\n *\n * Fires immediately BEFORE the `tool:unknown` substitute path so UI\n * consumers using `tool:before` to render a tool card still see\n * the model's attempt — otherwise the call was silently swallowed\n * and the model's intent never surfaced in the transcript.\n *\n * Pairing contract for `unknown: true` events:\n * - `tool:after` does NOT fire (the body never executed).\n * - `tool:error` fires next (unless the `tool:unknown` handler\n * sets `suppressError`).\n * - `tool:dispatched` fires with `outcome: 'unknown'`.\n *\n * Mutations to `ctx.input` / `ctx.coercions` on this path are\n * observational only — there's no body to dispatch them into.\n */\n unknown?: boolean\n }) => void\n /**\n * Symmetric notification fired ONCE per call the model dispatched,\n * regardless of the path the call ultimately took through the loop.\n * Every call that produces a tool_result closes with exactly one\n * terminal event, so pair `tool:dispatched` with that terminal to\n * reconstruct message history / close per-call spans:\n * - `outcome: 'execute'` → closes with `tool:after` OR, if the call was\n * cancelled in-flight via `agent.cancelTool`, with `tool:cancelled`\n * (NOT `tool:after`). A span keyed on `dispatched(execute)→after`\n * will leak on cancel — also listen for `tool:cancelled`.\n * - `outcome: 'gate-substitute'` → closes with `tool:after`.\n * - `outcome: 'gate-block' | 'invalid-input'` → no `tool:after` (the\n * body never ran); the dispatch event itself carries the result.\n * - `outcome: 'unknown'` → no `tool:after`; closes after\n * `tool:unknown` (+ optional `tool:error`).\n * The strict direction always holds: no `tool:after` fires without a\n * preceding `tool:dispatched`.\n *\n * Distinct from `tool:before`: `tool:before` fires when a tool's body\n * is about to execute (the \"I'm running this tool\" notification)\n * OR — as a special case — once with `unknown: true` for hallucinated\n * tool names, so UI consumers rendering tool cards still see the\n * model's attempt. It still skips gate-block / gate-substitute /\n * validation-reject paths. `tool:dispatched` fires on all five paths\n * with an `outcome` discriminator so live consumers never see a\n * `tool:after` whose matching dispatch event was suppressed.\n *\n * Strict ordering: `tool:dispatched` fires AFTER `tool:gate` /\n * `validation:reject` / `tool:unknown` so the resolved `outcome` is\n * always accurate. Fires BEFORE `tool:before` on the execute path so\n * consumers binding both events see dispatched-then-before.\n *\n * `reason` is set only when `outcome === 'gate-block'` and carries the\n * gate's refusal text (so consumers can render denied-diff badges, etc.\n * without a side channel).\n */\n 'tool:dispatched': (ctx: ToolHookContext & {\n outcome: 'execute' | 'gate-substitute' | 'gate-block' | 'unknown' | 'invalid-input'\n reason?: string\n runToolCounts: Readonly<Record<string, number>>\n }) => void\n /**\n * Fires after a tool body produced a result (the execute path) or a\n * `tool:gate` listener substituted one via `ctx.result` (the\n * cache-substitute path). Carries the post-`tool:transform` payload —\n * what the model actually sees on the wire.\n *\n * Does NOT fire on gate-block / unknown-tool / validation-reject\n * paths: the loop synthesizes their `tool_result` text inline and\n * sends it back to the model, but no tool body produced output and\n * no `tool:transform` ran. Consumers wanting symmetric per-call\n * notification across every dispatch path should listen to\n * `tool:dispatched` instead (which fires once per call regardless of\n * path, with an `outcome` discriminator).\n *\n * This narrow contract is deliberate: tracing spans opened in\n * `tool:before` only get closed where they were opened, byte budgets\n * only count real tool output, and chat-layer transcript renderers\n * keep their `tool` ↔ `tool-result` pairing without special-casing\n * the synthesized paths.\n */\n 'tool:after': (ctx: ToolHookContext & {\n result: string | ToolResultContent[]\n /**\n * Final wire error bit for this result, post-`tool:transform` (a\n * transform can flip it in either direction). Middleware that caches\n * or replays results (e.g. `dedupTools`) must consult this: error\n * results are state-dependent verdicts, not stable payloads.\n */\n isError: boolean\n outputBytes: number\n coercions?: readonly string[]\n runToolCounts: Readonly<Record<string, number>>\n /**\n * Structured side-effect outcome for mutating tools that reported one via\n * `ToolContext.reportOutcome` (built-in `write_file` / `edit` /\n * `multi_edit` do). `'noop'` means the call succeeded but changed zero\n * bytes — the signal hosts need to break no-progress loops without\n * string-matching the result text. Absent for read-only tools and any\n * tool that didn't report. See {@link ToolOutcome}.\n */\n outcome?: ToolOutcome\n }) => void\n /**\n * Fires when a tool throws during execution. Mutate `result` to substitute a\n * tool-output payload that gets sent back to the model in place of the\n * default `Tool error: <msg>` string — useful for OSS-model error rewriting\n * (collapse stack traces, hide internal paths, prepend recovery hints).\n *\n * The post-hook value flows through `tool:transform` like a normal output, so\n * downstream byte-budgeting and image-stripping still apply.\n */\n 'tool:error': (ctx: ToolHookContext & { error: Error, result?: string | ToolResultContent[] }) => void\n /** Fires after a tool produces output and before the result goes back to the model. Mutate `ctx.result` to rewrite it. `outputBytes` is the size before any mutation. */\n 'tool:transform': (ctx: ToolHookContext & { result: string | ToolResultContent[], isError: boolean, metadata: ToolResultMetadata, outputBytes: number, coercions?: readonly string[] }) => void\n /**\n * Fires once per artifact the session recorded from a tool result's\n * reserved `metadata.artifacts` namespace (see `session/artifacts.ts`).\n * Observability only — the artifact is already persisted; hosts use it\n * to refresh badges / panels live. Never fires on sessionless runs or\n * stores without artifact support.\n */\n 'artifact:recorded': (ctx: { runId: string, turnId: string, callId: string, name: string, artifact: SessionArtifact }) => void\n /** Bubbled from a subagent's `artifact:recorded` (shared-session children). Same payload + `childId` / `depth`. */\n 'child:artifact:recorded': (ctx: { runId: string, turnId: string, callId: string, name: string, artifact: SessionArtifact, childId: string, depth: number }) => void\n /**\n * Fires when a settled tool call's durable state (`ToolContext.state`) could\n * NOT be committed — the backing {@link AgentOptions.toolState} store\n * rejected the write.\n *\n * The call itself is unaffected: the tool body already ran and its result is\n * already on its way to the model, so failing the run over a bookkeeping\n * write would be worse than losing it. But losing it silently is worse\n * still — a dropped one-shot flag re-arms the exact bug durable tool state\n * exists to prevent — so this is the seam for alerting on it. `ops` is the\n * delta that did NOT land, in the order the tool staged it; replaying it by\n * hand is a legitimate recovery.\n *\n * Without a handler (or with one that leaves `handled` unset) the loop logs\n * to stderr. Set `handled = true` when your handler owns the reporting and\n * you don't want the duplicate log line.\n */\n 'tool-state:commit-failed': (ctx: {\n runId: string\n callId: string\n toolName: string\n ops: readonly ToolStateOp[]\n error: Error\n handled?: boolean\n }) => void\n /**\n * Fires before the generic \"Unknown tool\" error when the model invokes a tool\n * that isn't registered (hallucinated names, dropped MCP servers, dangling\n * aliases). Mutate `result` to substitute a friendly response or set\n * `suppressError: true` to skip the companion `tool:error` emission.\n *\n * Fires for any unknown tool name — including hallucinated MCP-style names\n * (`mcp_<server>_xxx`); branch on `name.startsWith('mcp_')` to differentiate.\n */\n 'tool:unknown': (ctx: ToolHookContext & {\n result?: string | ToolResultContent[]\n suppressError: boolean\n }) => void\n /**\n * Fires when a single in-flight tool call is cancelled mid-flight via\n * `agent.cancelTool(callId)` — typically the TUI's \"cancel this tool\"\n * affordance. Distinguished from `tool:error` (the tool body threw),\n * `agent:abort` (whole run aborted), and the batch-layer\n * `INTERRUPT_MESSAGE_FOR_TOOL_USE` shape (sibling drain on full abort)\n * so consumers can split the four causes.\n *\n * Semantics:\n * - The tool's body has already received an abort signal (via the\n * combined per-call + run signal); whether it actually unwound is\n * up to the body's signal awareness.\n * - The framework returns the canonical\n * {@link TOOL_USE_CANCELLED_MESSAGE} to the model with\n * `isError: true` so the assistant turn closes cleanly and the\n * model can continue with the next batch call.\n * - `tool:after` and `tool:transform` do **not** fire for a cancelled\n * call — there's no real result the loop should account for.\n *\n * `runToolCounts` mirrors the `tool:gate` / `tool:before` snapshot for\n * this call (frozen, pre-increment) so a single per-call observer can\n * subscribe to either hook without re-walking the run.\n */\n 'tool:cancelled': (ctx: ToolHookContext & {\n reason: string\n runToolCounts: Readonly<Record<string, number>>\n }) => void\n /**\n * Live output of a running tool (`ToolContext.reportProgress`): a codemode\n * script's printed items, a command's output chunks. Fires between\n * `tool:before` and the call's terminal event, never on replay of a\n * recorded call. Nothing here reaches the model or the session.\n */\n 'tool:progress': (ctx: ToolHookContext & { progress: ToolProgress }) => void\n /**\n * Fires when `validateToolArgs` rejects an input that could not be auto-coerced\n * to satisfy the tool's `inputSchema`. Observational — the tool call still\n * surfaces a `Validation error: …` string back to the model. Useful for\n * counting validation failures separately from runtime tool errors.\n */\n 'validation:reject': (ctx: ToolHookContext & {\n reason: string\n schema: Record<string, unknown>\n }) => void\n /**\n * Fires when `validateToolArgs` successfully auto-coerced one or more input\n * fields to satisfy the tool's `inputSchema`. **Only fires when at least one\n * coercion happened** — never on perfectly-shaped inputs. Useful for counting\n * model \"wrongness rate\" without re-running validation downstream.\n *\n * `coercions` lists the field names that were coerced. The values landed in\n * the input that the tool actually received; consumers wanting before/after\n * comparison can re-run `validateToolArgs(ctx.input, ctx.schema)`.\n */\n 'validation:coerce': (ctx: ToolHookContext & {\n coercions: readonly string[]\n schema: Record<string, unknown>\n }) => void\n\n // Context\n /** Fires per request with the session messages about to be sent. Mutate `ctx.messages` to rewrite the context for this turn. */\n 'context:transform': (ctx: { messages: SessionMessage[] }) => void\n /**\n * Fires per request, after `context:transform` and before the request goes\n * out. Mutating `ctx.system` updates the system prompt the provider sends\n * for this turn.\n *\n * ## Anything written here must be byte-stable across turns\n *\n * The system prompt sits ABOVE the whole conversation in the provider's cache\n * prefix — Anthropic hashes tools → system → messages, cumulatively — so a\n * one-byte change to `ctx.system` invalidates every message-level breakpoint\n * and re-bills the entire transcript at the cache-write rate. Only `tools` and\n * the static half of the system prompt survive, which is what makes this hard\n * to spot: `cacheRead` stays large and non-zero (it becomes a CONSTANT equal to\n * the scaffold) while cost climbs.\n *\n * The classic way to get this wrong is a live tally: interpolating something\n * like `You have already read ${n} files…` re-renders the prompt on most turns\n * and quietly re-bills the transcript on each one.\n *\n * Constant guidance strings, feature flags and run-stable facts are fine here.\n * Live counters, timestamps, elapsed times, per-turn indices and anything\n * derived from a growing tally are NOT — not even in the dynamic half below\n * `SYSTEM_PROMPT_BOUNDARY`, which carries no `cache_control` of its own but is\n * still inside the prefix that every message breakpoint hashes.\n *\n * Genuinely per-turn state belongs in the transcript, persisted as immutable\n * history so it cannot shift under the prefix on a later turn. A wire-only\n * append to the newest message does NOT work either: that message re-renders\n * without the append next turn and the prefix diverges there instead. Install\n * {@link installCacheBreakLogger} while developing anything that writes here.\n *\n * `messages` is read-only here; use `context:transform` for message\n * surgery. `session` is `undefined` when the run is sessionless.\n */\n 'system:transform': (ctx: { system: string, messages: readonly SessionMessage[], turn: number, turnId: string, session?: Session }) => void\n /** Fires when a queued {@link Agent.steer} message is injected into the conversation as a user turn. */\n 'steer:inject': (ctx: { message: string }) => void\n\n // Spawn\n /**\n * A top-level `spawn` call found every slot taken and waits for one (it\n * starts, with `spawn:before`, once a running child finishes). Never fires\n * for a subagent's spawn: that one is refused at the cap instead.\n */\n 'spawn:queued': (ctx: SpawnQueuedContext) => void\n /** Fires when a spawned subagent is about to start. */\n 'spawn:before': (ctx: SpawnHookContext) => void\n /** Fires when a spawned subagent finishes, with its run stats. */\n 'spawn:complete': (ctx: ChildRunStats) => void\n /** Fires when a spawned subagent fails. */\n 'spawn:error': (ctx: SpawnHookContext & { error: Error }) => void\n /**\n * Nested spawn lifecycle — a SUBAGENT's `spawn:before` / `spawn:complete` /\n * `spawn:error`, re-emitted on its parent's bus (and chained up to the\n * root). Payload is the spawned agent's record plus `parentChildId`, the\n * subagent that spawned it; see {@link ChildSpawnIdentity} for why there is\n * no `childId`. Observational copies: `child:spawn:before` carries no\n * `tracingContext` (the spawner's own tracer owns propagation). The\n * root's own `spawn:*` keeps firing for direct children only.\n */\n 'child:spawn:before': (ctx: ChildSpawnHookContext) => void\n /** Bubbled `spawn:complete` from a subagent. */\n 'child:spawn:complete': (ctx: ChildRunStats & ChildSpawnIdentity) => void\n /** Bubbled `spawn:error` from a subagent. */\n 'child:spawn:error': (ctx: ChildSpawnHookContext & { error: Error }) => void\n\n // Child hook bubbling — re-fires of a subagent's in-turn events on the\n // parent's hook bus. Each context is the original child event payload\n // augmented with `childId` (spawn id) and `depth` (subagent depth). Parents\n // opt in by listening; default behavior is silent. Grandchildren bubble\n // through their immediate parent's spawn tool, so a top-level listener\n // sees the full transitive stream.\n /** Bubbled `stream:text` from a subagent. See the comment above for how child events bubble. */\n 'child:stream:text': (ctx: StreamHookContext & { delta: string, text: string, childId: string, depth: number }) => void\n /** Bubbled `stream:thinking` from a subagent. */\n 'child:stream:thinking': (ctx: StreamHookContext & { delta: string, thinking: string, childId: string, depth: number }) => void\n /** Bubbled `stream:end` from a subagent. */\n 'child:stream:end': (ctx: StreamHookContext & { text: string, childId: string, depth: number }) => void\n /** Bubbled `stream:server_tool_use` from a subagent. See {@link AgentHooks['stream:server_tool_use']}. */\n 'child:stream:server_tool_use': (ctx: StreamHookContext & {\n id: string\n name: string\n input: Record<string, unknown>\n childId: string\n depth: number\n }) => void\n /** Bubbled `stream:server_tool_result` from a subagent. See {@link AgentHooks['stream:server_tool_result']}. */\n 'child:stream:server_tool_result': (ctx: StreamHookContext & {\n toolUseId: string\n toolName: string\n content: unknown\n childId: string\n depth: number\n }) => void\n /** Bubbled `stream:error` from a subagent's turn. See {@link AgentHooks['stream:error']}. */\n 'child:stream:error': (ctx: StreamHookContext & { err: unknown, childId: string, depth: number }) => void\n /** Bubbled {@link AgentHooks['stream:retry']} from a spawned child — lets hosts surface subagent retries. */\n 'child:stream:retry': (ctx: StreamHookContext & {\n attempt: number\n nextAttempt: number\n maxAttempts: number\n delayMs: number\n err: unknown\n statusCode?: number\n requestId?: string\n /** See {@link AgentHooks['stream:retry']}'s `discardedThinking`. */\n discardedThinking?: true\n childId: string\n depth: number\n }) => void\n /**\n * Gate-style child events. Unlike the other `child:*` events, the bubble\n * passes the **same `ctx` reference** the subagent's loop is awaiting on:\n * setting `ctx.block` / `ctx.reason` / `ctx.result` on a parent listener\n * propagates straight back to the child, refusing or substituting the call.\n *\n * Use these to gate subagent tool calls (native + MCP) from the parent\n * without registering listeners on every child agent. The parent's own\n * `tool:gate` / `mcp:tool:gate` listeners are NOT auto-shared with\n * children — that would also share their budgets and dedup state.\n */\n 'child:tool:gate': (ctx: ToolHookContext & ToolGateResumeFields & {\n block: boolean\n reason: string\n result?: string | ToolResultContent[]\n metadata: ToolResultMetadata\n runToolCounts: Readonly<Record<string, number>>\n childId: string\n depth: number\n }) => void\n /** Bubbled `mcp:tool:gate` from a subagent, with `childId` and `depth`. */\n 'child:mcp:tool:gate': (ctx: McpToolHookContext & {\n block: boolean\n reason: string\n result?: string | ToolResultContent[]\n childId: string\n depth: number\n }) => void\n /** Bubbled `tool:before` from a subagent, with `childId` and `depth`. */\n 'child:tool:before': (ctx: ToolHookContext & {\n coercions?: readonly string[]\n runToolCounts: Readonly<Record<string, number>>\n childId: string\n depth: number\n /**\n * Pre-write file snapshot — only set by the enricher in `spawn-child.ts`\n * when `ctx.name` is `write_file`, `edit` or `multi_edit` and the child\n * agent's execution context succeeded in reading the target path.\n * Absent for every other tool, and absent when the file didn't exist\n * (a fresh-create). The TUI's edit-diff renderer treats absence as an\n * empty old buffer to render an all-add view.\n */\n priorContent?: string\n }) => void\n /**\n * Bubbled sibling of {@link AgentHooks['tool:dispatched']} — fires when a\n * subagent dispatches a call. Symmetric with `child:tool:after` so live\n * UI rendering of subagent transcripts stays paired across all five\n * dispatch paths (gate-block/gate-substitute/unknown/invalid-input/execute).\n */\n 'child:tool:dispatched': (ctx: ToolHookContext & {\n outcome: 'execute' | 'gate-substitute' | 'gate-block' | 'unknown' | 'invalid-input'\n reason?: string\n runToolCounts: Readonly<Record<string, number>>\n childId: string\n depth: number\n }) => void\n /** Bubbled `tool:after` from a subagent, with `childId` and `depth`. */\n 'child:tool:after': (ctx: ToolHookContext & {\n result: string | ToolResultContent[]\n /** See `tool:after.isError`. Optional: bridges that synthesize this event (a child run in another invocation) may not know it. */\n isError?: boolean\n outputBytes: number\n coercions?: readonly string[]\n runToolCounts: Readonly<Record<string, number>>\n /** See `tool:after.outcome` — structured side-effect outcome for mutating tools. */\n outcome?: ToolOutcome\n childId: string\n depth: number\n }) => void\n /**\n * Same-ref bubble of a subagent's `tool:transform` hook. Parent\n * listeners may mutate `ctx.result` to rewrite the child tool's\n * output — used by the TUI to append per-edit annotation blocks\n * onto a child agent's `multi_edit` / `edit` / `write_file`\n * results when a parent-level approval gate produced a partial\n * decision.\n */\n 'child:tool:transform': (ctx: ToolHookContext & {\n result: string | ToolResultContent[]\n isError: boolean\n metadata: ToolResultMetadata\n outputBytes: number\n coercions?: readonly string[]\n childId: string\n depth: number\n }) => void\n /** Bubbled `tool:error` from a subagent. */\n 'child:tool:error': (ctx: ToolHookContext & { error: Error, childId: string, depth: number }) => void\n /** Bubbled sibling of {@link AgentHooks['repeat-guard:exceeded']} — a subagent tripped the repeat guard. */\n 'child:repeat-guard:exceeded': (ctx: {\n tool: string\n count: number\n threshold: number\n turnId: string\n action: 'block' | 'steer' | 'abort'\n childId: string\n depth: number\n }) => void\n /** Bubbled sibling of {@link AgentHooks['validation:reject']} — a subagent's call was rejected for invalid input. */\n 'child:validation:reject': (ctx: ToolHookContext & { reason: string, schema: Record<string, unknown>, childId: string, depth: number }) => void\n /**\n * Bubbled sibling of {@link AgentHooks['tool:cancelled']} — fires when a\n * subagent's in-flight tool was cancelled (either directly on the parent\n * via `agent.cancelTool(callId)` reaching a child's call, or by the child\n * agent's own consumers). Symmetric with `child:tool:error` and\n * `child:tool:after` so the TUI's transcript renderer can paint the\n * cancelled badge against the right subagent row.\n */\n 'child:tool:cancelled': (ctx: ToolHookContext & {\n reason: string\n runToolCounts: Readonly<Record<string, number>>\n childId: string\n depth: number\n }) => void\n /** Bubbled `tool:progress` from a subagent, with `childId` and `depth`. */\n 'child:tool:progress': (ctx: ToolHookContext & { progress: ToolProgress, childId: string, depth: number }) => void\n /** Bubbled from a subagent's `background:start`. Same payload + `childId` / `depth`. */\n 'child:background:start': (ctx: {\n taskId: string\n pid: number\n command: string\n cwd: string\n outputPath: string\n startedAt: number\n childId: string\n depth: number\n }) => void\n /** Bubbled from a subagent's `background:exit`. Same payload + `childId` / `depth`. */\n 'child:background:exit': (ctx: {\n taskId: string\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n outputPath: string\n durationMs: number\n command: string\n childId: string\n depth: number\n }) => void\n /**\n * Bubbled from a subagent's `background:reassign`. Fires when a\n * grandchild's tasks get promoted to the child's handle on the\n * grandchild's destroy — gives the parent a chance to mirror the\n * ownership change in any UI it maintains.\n */\n 'child:background:reassign': (ctx: {\n taskId: string\n fromHandleId: string\n toHandleId: string\n childId: string\n pid: number\n command: string\n outputPath: string\n startedAt: number\n depth: number\n }) => void\n /** Bubbled `turn:after` from a subagent. */\n 'child:turn:after': (ctx: {\n turn: number\n turnId: string\n usage: TurnUsage\n message: SessionTurn\n /** See `turn:after.aborted` — runtime emissions include it; treat absent as `false`. */\n aborted?: boolean\n toolCounts: { turn: Readonly<Record<string, number>>, run: Readonly<Record<string, number>> }\n childId: string\n depth: number\n }) => void\n\n // MCP server lifecycle\n /**\n * Fires once a server's client is live (transport connected, SDK handshake\n * complete). For servers bootstrapped eagerly, fires right after bootstrap;\n * for servers with `lazyConnect: true`, fires after the first `tools/call`\n * triggers the deferred connect — `lazy: true` lets the host distinguish.\n */\n 'mcp:connect': (ctx: { name: string, transport: string, tools: string[], lazy?: boolean }) => void\n /** Fires when an MCP server fails to connect or errors fatally. */\n 'mcp:error': (ctx: { name: string, error: Error }) => void\n /** Diagnostic line from a server. For fatal failures hook `mcp:error`. */\n 'mcp:warn': (ctx: { name: string, message: string }) => void\n /** Fires when an MCP server connection is closed. */\n 'mcp:close': (ctx: { name: string }) => void\n /**\n * Fires at the start of a per-server bootstrap attempt, before any network I/O.\n * Pairs with `mcp:bootstrap:end` and is always emitted, regardless of outcome.\n */\n 'mcp:bootstrap:start': (ctx: { name: string, transport: string }) => void\n /**\n * Fires at the end of a per-server bootstrap attempt. `durationMs` spans from\n * the matching `mcp:bootstrap:start`. On `ok: false` carries the originating\n * error so consumers can log / trace without relying on a separate `mcp:error`.\n *\n * `lazy: true` on the success arm signals the server registered its tools\n * without opening a connection (see `McpServerConfig.lazyConnect`); the\n * connect cost is deferred to the first `tools/call`. `cached: true` signals\n * the bootstrap skipped `tools/list` because the host provided\n * `cachedTools` — orthogonal to `lazy` (a server can be cached and eager,\n * cached and lazy, or neither).\n *\n * `warnings` (success arm only) collects soft signals that surfaced during\n * bootstrap without aborting it — currently just \"server rejected\n * `notifications/initialized` with 4xx\" from the tolerant client. Hosts\n * can render a \"connected with caveats\" badge from this; consumers that\n * don't care can ignore the field.\n */\n 'mcp:bootstrap:end': (ctx: { name: string, transport: string, durationMs: number } & ({ ok: true, toolCount: number, lazy?: boolean, cached?: boolean, warnings?: string[] } | { ok: false, error: Error })) => void\n /**\n * Fires exactly ONCE per bootstrap batch, after every server's attempt\n * has settled. `results` is sorted by server name — a deterministic\n * aggregate, unlike the per-server `mcp:bootstrap:end` events which\n * fire in network-completion order inside the parallel bootstrap.\n * Durable-execution hosts that journal bootstrap outcomes should hook\n * this instead of buffering + sorting the per-server stream.\n */\n 'mcp:bootstrap:settled': (ctx: {\n results: ReadonlyArray<{ name: string, transport: string, durationMs: number } & (\n | { ok: true, toolCount: number, lazy?: boolean }\n | { ok: false, skipped?: boolean, error?: Error }\n )>\n }) => void\n /**\n * Fires once per successful bootstrap with the **raw, pre-filter** tool\n * schemas the server advertised via `tools/list` (or that the host\n * pre-seeded via `McpServerConfig.cachedTools`). Useful for hosts that\n * want to:\n *\n * - Persist a per-server tool catalog to disk so a settings UI can\n * show every available tool — including ones currently hidden by\n * `disabledTools` — without re-bootstrapping.\n * - Build per-tool toggle UIs that reflect the full server surface.\n * - Diff successive bootstraps to detect newly-added / removed tools.\n *\n * Read-only — handlers must NOT mutate `tools`. Shape the model-facing\n * list via `mcp:tools:filter` instead (which fires immediately after\n * this hook and IS the mutation surface).\n *\n * Does NOT fire when the bootstrap fails. Does NOT fire on lazy-connect\n * servers' deferred first-call (the schemas were already known from\n * `cachedTools` at the agent-construction boundary).\n */\n 'mcp:tools:list': (ctx: { name: string, transport: 'stdio' | 'sse' | 'streamable-http', tools: readonly McpToolSchema[] }) => void\n /**\n * Fires when a server's bootstrap was skipped because it requires OAuth\n * login and no tokens are stored. The host (TUI) surfaces this as a\n * \"needs-auth\" status so the user can opt in to interactive login via\n * `loginMcpServer`. Pairs with `mcp:bootstrap:end ok:false` — both fire,\n * since the bootstrap did fail (just for a recoverable reason).\n *\n * `reason`:\n * - `'no-tokens'` — config has `auth: 'oauth'` set (or normalized from\n * Cursor's `authMethod: 'mcpOAuth'`) and the credential store is empty.\n * - `'auto-promoted'` — server returned 401 + RFC 9728 metadata. No\n * static Authorization header was set, so the server is eligible for\n * OAuth login even though the config didn't explicitly request it.\n */\n 'mcp:auth:required': (ctx: {\n name: string\n transport: 'stdio' | 'sse' | 'streamable-http'\n reason: 'no-tokens' | 'auto-promoted'\n }) => void\n /**\n * Fires during an INTERACTIVE OAuth login flow — when the SDK wants the\n * user agent to navigate to the authorization URL. Hosts open a browser\n * and surface the URL in the TUI (in case the browser auto-open fails).\n * Distinct from `mcp:auth:required` (which fires from bootstrap when\n * tokens are missing); this only fires from `loginMcpServer`.\n */\n 'mcp:auth:url': (ctx: { name: string, url: string }) => void\n /** Interactive login completed and tokens were persisted. */\n 'mcp:auth:success': (ctx: { name: string }) => void\n /** Interactive login failed (browser error, abort, exchange failure). */\n 'mcp:auth:error': (ctx: { name: string, error: Error }) => void\n /**\n * Fires once per server after `listTools()` and after the config-side filters\n * (`enabledTools` / `disabledTools` / `toolFilter`) have applied, but BEFORE\n * tools are registered. Handlers may mutate `ctx.tools` in place — splicing,\n * reordering, or replacing entries — to further narrow what the model sees.\n *\n * Composes with config-side filters: config drops tools the host's static\n * policy excludes; this hook is the runtime escape hatch for per-user, per-\n * environment, or capability-driven decisions that the config can't express.\n *\n * Items are upstream tool descriptors (NOT yet namespaced as `mcp_<server>_<tool>`).\n */\n 'mcp:tools:filter': (ctx: {\n server: string\n transport: 'stdio' | 'sse' | 'streamable-http'\n tools: Array<{ name: string, description?: string | null, inputSchema?: unknown }>\n }) => void\n\n // MCP tool execution\n /**\n * MCP-side counterpart of `tool:gate`. Same shape: set `block` to refuse,\n * set `result` to substitute a successful payload and skip the upstream\n * MCP `callTool`. When both are set across the handler chain, `block` wins.\n *\n * Fires INSIDE the MCP wrapper's `execute`, after the loop's `tool:gate`\n * already ran. Does **not** carry `runToolCounts` — those are loop-level\n * and already exposed on `tool:gate` for MCP tools (which are registered\n * as agent tools under their namespaced name `mcp_<server>_<tool>`). Use\n * `tool:gate` for budget / dedup logic; reserve `mcp:tool:gate` for\n * MCP-specific concerns (per-server routing, transport-aware refusals).\n */\n 'mcp:tool:gate': (ctx: McpToolHookContext & {\n block: boolean\n reason: string\n result?: string | ToolResultContent[]\n }) => void\n /** Fires before an MCP tool call is sent to the server. */\n 'mcp:tool:before': (ctx: McpToolHookContext) => void\n /** Fires after an MCP tool call returns, with the result. */\n 'mcp:tool:after': (ctx: McpToolHookContext & { result: string | ToolResultContent[], outputBytes: number }) => void\n /** Fires before the MCP tool result is returned. Mutate `ctx.result` to rewrite it. `outputBytes` is the size before any mutation. */\n 'mcp:tool:transform': (ctx: McpToolHookContext & { result: string | ToolResultContent[], outputBytes: number }) => void\n /** Fires when an MCP tool call throws. */\n 'mcp:tool:error': (ctx: McpToolHookContext & { error: Error }) => void\n\n // Background tasks\n /**\n * Fires when a background task is spawned via `ExecutionContext.execBackground`.\n * Observational — telemetry consumers can mint a \"task started\" span.\n *\n * `outputPath` is the file the context is streaming stdout+stderr to;\n * the model reads it via `read_file` on demand. `pid` is the OS pid of\n * the spawned shell wrapper (a process-group leader on POSIX, so a\n * `process.kill(-pid)` on cleanup tears down the whole subtree).\n */\n 'background:start': (ctx: {\n taskId: string\n pid: number\n command: string\n cwd: string\n outputPath: string\n startedAt: number\n }) => void\n\n /**\n * Fires exactly once per task when the child process terminates —\n * natural exit, host-issued kill, or `agent.destroy()` teardown. Same\n * payload the agent enqueues into `pendingTaskNotifications` for\n * injection on the next turn; useful for hosts that want to render\n * a \"task done\" toast immediately rather than wait for the model's\n * next prompt.\n */\n 'background:exit': (ctx: {\n taskId: string\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n outputPath: string\n durationMs: number\n command: string\n }) => void\n\n /**\n * Ownership of a background task was transferred from one execution\n * handle to another — typically the spawn tool reassigning a\n * subagent's still-running tasks up to the parent's handle so they\n * outlive the subagent's `destroy()`.\n *\n * Hosts that listed the task under the source handle should update\n * their view to reflect the new owner. The TUI's `backgroundTasks`\n * registry drops the `childId` tag on the matching entry so the\n * `ctrl+k` picker shows it as parent-level (and `killBackgroundTask`\n * actually finds it).\n *\n * Fires on the new owner's hook bus (the parent's, in the spawn\n * case). The old owner is typically already torn down — wiring the\n * event the other way would deliver it to a destroyed hookable.\n */\n 'background:reassign': (ctx: {\n taskId: string\n /** `ExecutionHandle.id` of the previous owner. */\n fromHandleId: string\n /** `ExecutionHandle.id` of the new owner. */\n toHandleId: string\n /** Subagent label (`child-N`) the task originated under, when known. */\n childId?: string\n pid: number\n command: string\n outputPath: string\n startedAt: number\n }) => void\n\n /**\n * A still-running background task was handed to THIS agent by its host —\n * the agent was built on a host-owned handle that outlived the previous\n * agent (a rebuild after a model swap, settings change, MCP login…; see\n * `chat/execution-cache.ts`). The task's exit/stall callbacks now fire on\n * this bus. Hosts that track running tasks register it exactly like\n * `background:start`; unlike that hook it does not mean \"just spawned\".\n */\n 'background:adopt': (ctx: {\n taskId: string\n pid: number\n command: string\n cwd: string\n outputPath: string\n startedAt: number\n }) => void\n\n /**\n * The stall watchdog ({@link AgentBehavior.backgroundStallWatchdogMs})\n * detected a background task with no output for the configured window.\n * One-shot per quiet period — re-arms after fresh output. The agent\n * queues a `<task-stall>` notification for the model's next turn;\n * hosts can additionally render a \"task may be stuck\" hint.\n */\n 'background:stall': (ctx: {\n taskId: string\n command: string\n outputPath: string\n stalledForMs: number\n bytesWritten: number\n }) => void\n\n /**\n * Fires at the start of an `agent.run()` that has background exits to\n * deliver, BEFORE they are rendered into `<task-notification>`\n * blocks (runs with nothing to deliver skip the hook). `exits` merges\n * the push-fed queue (`background:exit` arrivals since the last run)\n * with a pull-based reconcile against\n * `ExecutionContext.listBackground` — so exits land even when the\n * context never calls `onExit` (remote / durable contexts can't push\n * from a timer into a host journal).\n *\n * This is the journalable seam for durable-execution hosts: the hook\n * runs inside the `run()` await chain, so a handler may replace\n * `ctx.exits` with a recorded copy, making the wall-clock-derived\n * `durationMs`, and so the whole `<task-notification>` block,\n * replay-stable. A replay-mode durable runtime already records the\n * `listBackground` poll as a `reconcile` step.\n *\n * Mutable: handlers may reorder, filter, or substitute entries. The\n * post-hook array is what the model sees.\n */\n 'background:reconcile': (ctx: { exits: TaskExitInfo[] }) => void\n\n /**\n * Fires exactly once per agent, the first time the execution handle is bound\n * — right after the lazy `execution.spawn()` (or immediately on the first run\n * when a handle was injected via {@link AgentOptions.handle}). `spawned` is\n * `true` when the agent minted the handle, `false` for an injected one.\n *\n * The seam durable hosts use to record the minted handle id (e.g. a sandbox\n * id) for re-attach on replay/resume — journal it here, then re-attach via\n * `AgentOptions.handle` + the provider's reconnect option on the next\n * invocation. Observational; the handle is not mutable.\n */\n 'execution:ready': (ctx: { handle: ExecutionHandle, spawned: boolean }) => void\n\n // Skills\n /** Fires once skills have been resolved, with the resolved skill list. */\n 'skills:resolve': (ctx: { skills: SkillConfig[] }) => void\n /** Fires with the rendered skills catalog text and the skills it lists. */\n 'skills:catalog': (ctx: { catalog: string, skills: SkillConfig[] }) => void\n /** Fires when a skill is activated. `via` says what triggered it. */\n 'skills:activate': (ctx: { skill: SkillConfig, via: ActivationVia }) => void\n /** Fires when a skill is deactivated. `reason` says why. */\n 'skills:deactivate': (ctx: { skill: SkillConfig, reason: DeactivationReason }) => void\n\n // Pairing repair\n /**\n * Fires once per repair performed by the pre-send pairing pass\n * ({@link ensureToolResultPairing}). The pass runs immediately before every\n * provider call and patches over the wire-level corruption modes that\n * Anthropic / OpenAI 400 on (orphan `tool_use` / `tool_result`, duplicate\n * ids, compaction-stranded blocks).\n *\n * Observational — handlers can mirror to Sentry/PostHog/etc. for\n * postmortems on new corruption sources but cannot suppress the repair.\n * The companion `behavior.strictToolPairing` flag is the consumer escape\n * hatch for \"throw instead of repair\" (training-data collectors).\n *\n * `turnId` lets handlers correlate a repair burst with the run-loop turn\n * that observed it. Absent on repairs detected from the synthetic\n * fallback path in `agent.run()`'s error catch (the orphan-tool_use\n * fallback for crash recovery), where no turn is in flight.\n */\n 'pairing:repair': (ctx: PairingRepair & { turnId?: string }) => void\n\n // Observability seams\n /**\n * Privacy seam invoked by observability adapters (tracing, metrics,\n * logging, persistence sinks) before attaching potentially-sensitive\n * payloads as span attributes / log lines / external storage.\n *\n * Handlers mutate `ctx.redacted` — the field starts equal to `value` and\n * the final post-hook string is what consumers ship downstream. Default\n * (no handlers) is no redaction. Multiple handlers compose left-to-right;\n * the last writer wins.\n *\n * `kind` lets a single handler do shape-aware scrubbing (system prompts\n * vs. tool args vs. tool output have very different secret surfaces).\n * `meta` carries context-specific identifiers (`toolName`, `server`,\n * `turnId`, `callId`) the adapter knows at the call site — handlers use\n * it for policy decisions (\"skip redaction on internal `read_file`\n * results\"). Adapters that don't fire this hook simply attach raw values.\n *\n * Synchronous-only by design: observability stays on the hot path. Heavy\n * scrubbing (regex over MB of output) belongs in `tool:transform` instead.\n */\n 'tracing:redact': (ctx: {\n kind:\n | 'system'\n | 'prompt'\n | 'tool-input'\n | 'tool-result'\n | 'mcp-tool-input'\n | 'mcp-tool-result'\n | 'stream-text'\n | 'stream-thinking'\n | 'decision-answers'\n value: string\n redacted: string\n meta?: Readonly<Record<string, unknown>>\n }) => void\n\n // Usage and output\n /** Fires after each turn with that turn's token usage and the run's cumulative input and output tokens. */\n 'usage': (ctx: { turn: number, turnId: string, usage: TurnUsage, totalIn: number, totalOut: number }) => void\n /** Fires with the structured output the model produced and the schema it was validated against. */\n 'output': (ctx: { output: Record<string, unknown>, schema: Record<string, unknown> }) => void\n /**\n * Fires when a turn's total tool-output bytes exceed `behavior.toolOutputBudget`.\n * Measured post-`tool:transform`. Loop injects a synthetic user message after\n * the tool-results turn instructing the model to summarize.\n */\n 'budget:exceeded': (ctx: { turn: number, turnId: string, bytes: number, budget: number }) => void\n /**\n * Fires when a per-tool budget configured via `behavior.toolBudgets` is\n * exceeded for a specific tool. `mode` reflects how the framework reacted:\n * `'steer'` lets the call run and queues a post-turn nudge; `'block'`\n * refuses the call outright with `Blocked: <message>`.\n *\n * `count` is the run-cumulative dispatched count just before this call.\n * Use `turnId` to correlate with `turn:after` if you need the integer turn\n * index. Distinct from `budget:exceeded` (byte-level) so consumers can\n * subscribe specifically; both can fire in the same turn.\n */\n 'tool-budget:exceeded': (ctx: {\n tool: string\n count: number\n max: number\n turnId: string\n mode: 'steer' | 'block'\n }) => void\n /**\n * Fires when the consecutive-identical {@link AgentBehavior.repeatGuard}\n * escalates on a tracked tool. In chain mode, `count` is the number of\n * back-to-back identical calls including the offending one; in window mode it\n * is the number of occurrences inside the configured window. `action`\n * reflects the escalation: `'block'` refused this call via `tool:gate`;\n * `'steer'` let it execute and appended a steering message to the result;\n * `'abort'` additionally aborts the run via the agent's `AbortController`\n * (the loop then fires `agent:abort`). The `'abort'` event fires BEFORE the\n * abort so consumers can distinguish a guard abort from a user-driven cancel.\n */\n 'repeat-guard:exceeded': (ctx: {\n tool: string\n count: number\n threshold: number\n turnId: string\n action: 'block' | 'steer' | 'abort'\n }) => void\n\n // Agent lifecycle\n /**\n * The run was aborted — fires exactly once per aborted run, on every abort\n * exit: a between-turn cancel (the loop's `signal.aborted` check), a\n * `repeat-guard` abort, AND a cancel that lands mid-stream (which throws out\n * of the turn and is caught at the run boundary). For a guard abort a\n * `repeat-guard:exceeded` (`action: 'abort'`) event fired first, so consumers\n * can attribute the cause.\n *\n * Does NOT fire on graceful non-cancel terminations that still finalize the\n * session run as `'aborted'`: `maxConsecutivePauseTurns` exhaustion and the\n * `maxCostUsd`/`maxTotalTokens` budget breaker (the latter throws\n * `AgentBudgetExceededError`). Branch on those via their own signals\n * (`finishReason: 'pause'`, the typed error) rather than `agent:abort`.\n *\n * `runId` — the aborted run, for correlating with `turn:after` / telemetry\n * spans. `reason` — the string passed to `AbortController.abort(reason)` when\n * one was supplied (mirrors `signal.reason`); absent for a bare `abort()`.\n */\n 'agent:abort': (ctx: { runId?: string, reason?: string }) => void\n /**\n * Run finished — fires on all exit paths (completion, maxTurns, abort, error).\n *\n * The `AgentStats` carried here is **cumulative** across the\n * parent agent loop and every recursively-spawned sub-agent\n * (`totalIn` / `totalOut` / `cost` / `totalCacheRead` / `totalCacheCreation`).\n * For parent-loop-only counts use `ctx.turnUsage` (parent-only array);\n * for tree-wide turn counts use `flattenTurns(ctx).length`.\n */\n 'agent:done': (ctx: AgentStats) => void\n /**\n * Durable-execution replay finished — the invocation transitioned from\n * journal replay to live execution (the first non-journaled provider\n * stream or tool body just started).\n *\n * The framework core never fires this: it is emitted by durable-runtime\n * adapters (see `createReplayTracker({ hooks })` in `zidane/restate`) once\n * per handler invocation. Hosts that would otherwise infer liveness\n * from side channels (counting live provider calls, watching stream\n * hooks) should key on this instead. Fires at most once per invocation;\n * never fires when the journal replays to completion without going\n * live (nothing left to execute — and nothing left to abort).\n */\n 'replay:done': (ctx: Record<string, never>) => void\n\n // Session\n /** Fires when a run starts on a session, with the run id and prompt label. */\n 'session:start': (ctx: SessionHookContext & { runId: string, prompt: string }) => void\n /** Fires when a run ends, with its status and the range of turn indexes it produced. */\n 'session:end': (ctx: SessionHookContext & { runId: string, status: SessionEndStatus, turnRange: [number, number] }) => void\n /** Fires when new turns are flushed to the session, with the turns and the resulting turn count. */\n 'session:turns': (ctx: SessionHookContext & { turns: SessionTurn[], count: number }) => void\n /** Fires when a session metadata key is set. */\n 'session:meta': (ctx: SessionHookContext & { key: string, value: unknown }) => void\n /** Fires when the session is saved. */\n 'session:save': (ctx: SessionHookContext) => void\n\n // Decisions\n /**\n * Fires when `agent.decide` / `ctx.decide` starts a decision, before\n * validation and the provider call. `provider` / `model` are absent when no\n * decision provider could be resolved. Contexts of `decision:*` hooks carry\n * no `decide`, so a decision can't recursively trigger another.\n */\n 'decision:start': (ctx: DecisionStartHookContext) => void\n /**\n * Fires once per decision, success or failure, after `decision:start`.\n * `ok: false` carries the failure `reason`; `ok: true` carries `answers`\n * and `usage` (decision usage is NOT folded into run totals).\n */\n 'decision:end': (ctx: DecisionEndHookContext) => void\n}\n\n/**\n * Authoritative list of hook event names. Kept in sync with `AgentHooks` at\n * compile time: `satisfies` rejects names not in the interface, and\n * `MissingHookEventNames` below rejects interface keys missing from the list.\n */\nconst HOOK_EVENT_NAMES = [\n 'system:before',\n 'agent:start',\n 'turn:before',\n 'turn:after',\n 'tool-results:after',\n 'tool-batch:resumed',\n 'resume:mismatch',\n 'stream:start',\n 'stream:text',\n 'stream:end',\n 'stream:thinking',\n 'stream:progress',\n 'stream:server_tool_use',\n 'stream:server_tool_result',\n 'stream:error',\n 'stream:retry',\n 'model:fallback',\n 'context:overflow',\n 'cache:break',\n 'cache:drift',\n 'compact:start',\n 'compact:progress',\n 'compact:end',\n 'history:repair',\n 'provider:input-transformations',\n 'oauth:refresh',\n 'provider:diagnostic',\n 'content-ref:resolve',\n 'tool:gate',\n 'tool:dispatched',\n 'tool:before',\n 'tool:after',\n 'tool:error',\n 'tool:cancelled',\n 'tool:progress',\n 'tool:transform',\n 'tool:unknown',\n 'tool-state:commit-failed',\n 'artifact:recorded',\n 'child:artifact:recorded',\n 'validation:reject',\n 'validation:coerce',\n 'context:transform',\n 'system:transform',\n 'steer:inject',\n 'spawn:queued',\n 'spawn:before',\n 'spawn:complete',\n 'spawn:error',\n 'child:spawn:before',\n 'child:spawn:complete',\n 'child:spawn:error',\n 'child:stream:text',\n 'child:stream:thinking',\n 'child:stream:end',\n 'child:stream:server_tool_use',\n 'child:stream:server_tool_result',\n 'child:stream:error',\n 'child:stream:retry',\n 'child:tool:gate',\n 'child:mcp:tool:gate',\n 'child:tool:dispatched',\n 'child:tool:before',\n 'child:tool:after',\n 'child:tool:transform',\n 'child:tool:error',\n 'child:validation:reject',\n 'child:repeat-guard:exceeded',\n 'child:tool:cancelled',\n 'child:tool:progress',\n 'child:background:start',\n 'child:background:exit',\n 'child:background:reassign',\n 'child:turn:after',\n 'mcp:connect',\n 'mcp:error',\n 'mcp:warn',\n 'mcp:close',\n 'mcp:bootstrap:start',\n 'mcp:bootstrap:end',\n 'mcp:bootstrap:settled',\n 'mcp:tools:list',\n 'mcp:auth:required',\n 'mcp:auth:url',\n 'mcp:auth:success',\n 'mcp:auth:error',\n 'mcp:tools:filter',\n 'mcp:tool:gate',\n 'mcp:tool:before',\n 'mcp:tool:after',\n 'mcp:tool:transform',\n 'mcp:tool:error',\n 'background:start',\n 'background:exit',\n 'background:reassign',\n 'background:adopt',\n 'background:stall',\n 'background:reconcile',\n 'execution:ready',\n 'skills:resolve',\n 'skills:catalog',\n 'skills:activate',\n 'skills:deactivate',\n 'usage',\n 'output',\n 'budget:exceeded',\n 'tool-budget:exceeded',\n 'repeat-guard:exceeded',\n 'pairing:repair',\n 'tracing:redact',\n 'agent:abort',\n 'agent:done',\n 'replay:done',\n 'session:start',\n 'session:end',\n 'session:turns',\n 'session:meta',\n 'session:save',\n 'decision:start',\n 'decision:end',\n] as const satisfies readonly (keyof AgentHooks)[]\n\ntype MissingHookEventNames = Exclude<keyof AgentHooks, (typeof HOOK_EVENT_NAMES)[number]>\ntype AssertNever<T extends never> = T\n// Fails to compile when an `AgentHooks` key is absent from `HOOK_EVENT_NAMES`.\ntype _HookEventNamesComplete = AssertNever<MissingHookEventNames>\n\nconst HOOK_EVENT_SET: ReadonlySet<string> = new Set(HOOK_EVENT_NAMES)\n\nexport function isKnownHookEvent(event: string): event is keyof AgentHooks {\n return HOOK_EVENT_SET.has(event)\n}\n\n/**\n * Strongly-typed hook registration map accepted on {@link AgentOptions.hooks}\n * (and therefore on any {@link Preset}). Each entry is a single handler or an\n * array of handlers — arrays are what {@link composePresets} produces when\n * multiple presets register handlers for the same event.\n *\n * Handlers registered here live for the **lifetime of the agent**. They fire\n * across every `run()`, mirroring a manual `agent.hooks.hook(event, fn)` call\n * made right after `createAgent` returns. For per-run handlers (auto-detached\n * at run end) use {@link AgentRunOptions.hooks} instead.\n */\nexport type AgentHookMap = Partial<{\n [K in keyof AgentHooks]: AgentHookHandlers[K] | AgentHookHandlers[K][]\n}>\n\n/** Present on every hook context except `decision:*` (see {@link AgentHookHandlers}). */\nexport interface DecideHookContext {\n /**\n * Make a decision request from inside a hook. Bound to the active run: its\n * abort signal cancels the decision and `decision:*` hooks carry its run id.\n * Never throws; see {@link AgentDecide}.\n */\n readonly decide: AgentDecide\n}\n\ntype WithDecide<F> = F extends (ctx: infer C, ...rest: infer R) => infer Ret\n ? (ctx: C extends object ? C & DecideHookContext : C, ...rest: R) => Ret\n : F\n\n/**\n * Handler signatures as consumers see them: identical to {@link AgentHooks}\n * except object contexts also carry {@link DecideHookContext.decide}. The\n * agent attaches it (non-enumerable) right before handlers run, so code that\n * fires hooks builds contexts without it.\n */\nexport type AgentHookHandlers = {\n [K in keyof AgentHooks]: K extends `decision:${string}` ? AgentHooks[K] : WithDecide<AgentHooks[K]>\n}\n\n/** Registration overloads that type handler contexts with `decide`. */\nexport interface AgentHookRegistration {\n hook: <K extends keyof AgentHooks>(name: K, fn: AgentHookHandlers[K], options?: { allowDeprecated?: boolean }) => () => void\n hookOnce: <K extends keyof AgentHooks>(name: K, fn: AgentHookHandlers[K]) => () => void\n removeHook: <K extends keyof AgentHooks>(name: K, fn: AgentHookHandlers[K]) => void\n}\n\n/**\n * `agent.hooks`: the agent's hookable, with registration typed through\n * {@link AgentHookHandlers} (so handlers see `ctx.decide`) while `callHook`\n * keeps the raw {@link AgentHooks} context shapes.\n */\nexport type AgentHookBus = AgentHookRegistration & Hookable<AgentHooks>\n\n/** Events whose contexts never receive `decide`. */\nexport function isDecisionHookEvent(event: string): boolean {\n return event.startsWith('decision:')\n}\n","/**\n * The agent-side decider behind `agent.decide` and `ctx.decide`.\n *\n * Resolves the decision provider + model, validates the request, enforces a\n * wall-clock cap combined with the run's abort signal, fires\n * `decision:start` / `decision:end`, and turns every failure into a\n * `{ ok: false, reason }` outcome. It never throws unless the caller passes\n * `{ throw: true }`.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent-hooks'\nimport type { Provider, ProviderDecisionRequest, ProviderDecisionResponse, ProviderDecisions } from '../providers'\nimport type { SerializedDecisionError } from './errors'\nimport type {\n AgentDecide,\n AgentDecisionsOptions,\n DecisionAnswers,\n DecisionEndHookContext,\n DecisionFailure,\n DecisionFailureReason,\n DecisionOutcome,\n DecisionQuestions,\n DecisionRequest,\n DecisionStartHookContext,\n} from './types'\nimport { randomUUID } from 'node:crypto'\nimport { createDebugLog } from '../debug-log'\nimport { abortError, classifyDecisionError, DecisionError, DecisionTimeoutError, DecisionUnavailableError, deserializeDecisionError, serializeDecisionError } from './errors'\nimport { decisionInputKind, validateDecisionAnswers, validateDecisionRequest } from './wire'\n\nexport * from './errors'\nexport { currentRunDecide, withRunDecide } from './run-scope'\nexport type * from './types'\nexport { decisionInputKind, scoreLevelDescription, scoreLevelLabel, validateDecisionAnswers, validateDecisionRequest } from './wire'\n\n/** Default wall-clock cap for one decision, in milliseconds. */\nexport const DEFAULT_DECISION_TIMEOUT_MS = 5000\n\nconst debugLog = createDebugLog('decisions')\n\nexport interface DeciderOptions {\n providers: readonly Provider[]\n /** `false` disables decisions; `undefined` auto-detects from `providers`. */\n config: AgentDecisionsOptions | false | undefined\n hooks: Hookable<AgentHooks>\n}\n\nexport interface Decider {\n /**\n * `decide` bound to a run: `runId` is stamped on `decision:*` hooks and\n * `runSignal` (that run's abort signal) cancels its decisions. With a\n * `journal`, pass `newId` too (the run's journaled ids), so a replayed\n * decision keeps its `decisionId`.\n */\n forRun: (runId: string | undefined, runSignal?: AbortSignal, journal?: DecisionJournal, newId?: () => string) => AgentDecide\n}\n\n/**\n * Records a decision's provider call as one step of a replay-mode run: a\n * replay gets the recorded outcome instead of asking the provider again.\n */\nexport type DecisionJournal = <T>(work: () => Promise<T>) => Promise<T>\n\ntype JournaledDecision\n = | { ok: true, response: ProviderDecisionResponse }\n | { ok: false, error: SerializedDecisionError }\n\ninterface Target {\n provider: Provider\n decisions: ProviderDecisions\n}\n\nfunction canDecide(decisions: ProviderDecisions): boolean {\n try {\n return decisions.availability().available\n }\n catch {\n return false\n }\n}\n\nexport function createDecider(options: DeciderOptions): Decider {\n const { providers, config, hooks } = options\n const unavailableModels = new Set<string>()\n let warnedUnavailable = false\n\n function resolveTarget(): Target | string {\n if (config === false)\n return 'Decisions are disabled for this agent'\n // Auto-detect: the first provider that can decide now (credentials), else\n // the first with the capability so its availability error is reported.\n const provider = config?.provider ?? providers.find(p => p.decisions && canDecide(p.decisions)) ?? providers.find(p => p.decisions)\n if (!provider)\n return `No configured provider supports decisions (checked: ${providers.map(p => p.name).join(', ') || 'none'})`\n if (!provider.decisions)\n return `Provider \"${provider.name}\" does not support decisions`\n return { provider, decisions: provider.decisions }\n }\n\n function modelKey(provider: Provider, model: string): string {\n return `${provider.name}/${model}`\n }\n\n function isAvailable(): boolean {\n const target = resolveTarget()\n if (typeof target === 'string')\n return false\n const model = config ? config.model ?? target.decisions.defaultModel : target.decisions.defaultModel\n if (unavailableModels.has(modelKey(target.provider, model)))\n return false\n return canDecide(target.decisions)\n }\n\n async function safeCallHook<K extends 'decision:start' | 'decision:end'>(name: K, ctx: Parameters<AgentHooks[K]>[0]): Promise<void> {\n try {\n await (hooks.callHook as (name: K, ctx: Parameters<AgentHooks[K]>[0]) => Promise<unknown> | void)(name, ctx)\n }\n catch (err) {\n debugLog(`${name} hook threw`, err)\n }\n }\n\n async function run(runId: string | undefined, runSignal: AbortSignal | undefined, request: DecisionRequest, journal: DecisionJournal | undefined, decisionId: string): Promise<DecisionOutcome> {\n const startedAt = Date.now()\n const questionNames = request && typeof request.questions === 'object' && request.questions ? Object.keys(request.questions) : []\n const inputKind = request?.input === undefined ? 'text' : decisionInputKind(request.input)\n const base: DecisionStartHookContext = { decisionId, ...(runId ? { runId } : {}), questionNames, inputKind, startedAt }\n\n const fail = async (reason: DecisionFailureReason, message: string, extra: Partial<DecisionFailure> = {}): Promise<DecisionFailure> => {\n const outcome: DecisionFailure = { ok: false, decisionId, reason, message, ...extra }\n if (reason === 'unavailable' && !warnedUnavailable) {\n warnedUnavailable = true\n debugLog('decisions unavailable', message)\n }\n const end: DecisionEndHookContext = {\n ...base,\n ...(extra.provider ? { provider: extra.provider } : {}),\n ...(extra.model ? { model: extra.model } : {}),\n durationMs: Date.now() - startedAt,\n ok: false,\n reason,\n message,\n }\n await safeCallHook('decision:end', end)\n return outcome\n }\n\n const target = resolveTarget()\n if (typeof target === 'string') {\n await safeCallHook('decision:start', base)\n return fail('unavailable', target)\n }\n const { provider, decisions } = target\n const model = request?.model ?? (config ? config.model : undefined) ?? decisions.defaultModel\n const started: DecisionStartHookContext = { ...base, provider: provider.name, model }\n await safeCallHook('decision:start', started)\n const who = { provider: provider.name, model }\n\n const problem = validateDecisionRequest(request?.input, request?.questions)\n if (problem)\n return fail('invalid', problem, who)\n if (unavailableModels.has(modelKey(provider, model)))\n return fail('unavailable', `Decision model \"${model}\" is unavailable on ${provider.name}`, who)\n // A journaled decision checks availability inside its step, so a replay\n // can't take another branch when a key is missing on replay.\n if (!journal) {\n let availability\n try {\n availability = decisions.availability()\n }\n catch (err) {\n availability = { available: false as const, reason: err instanceof Error ? err.message : String(err) }\n }\n if (!availability.available)\n return fail('unavailable', availability.reason, who)\n }\n\n const timeoutMs = request.timeoutMs ?? (config ? config.timeoutMs : undefined) ?? DEFAULT_DECISION_TIMEOUT_MS\n // setTimeout fires at once for anything outside (0, 2^31-1].\n if (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2 ** 31 - 1)\n return fail('invalid', `timeoutMs must be a number of milliseconds in (0, ${2 ** 31 - 1}]`, who)\n const timeoutController = new AbortController()\n const timer = setTimeout(() => timeoutController.abort(new DecisionTimeoutError(`Decision timed out after ${timeoutMs}ms`)), timeoutMs)\n const signals = [timeoutController.signal, runSignal, request.signal].filter((s): s is AbortSignal => s !== undefined)\n const signal = AbortSignal.any(signals)\n\n let response: ProviderDecisionResponse\n try {\n if (signal.aborted)\n throw abortError(signal)\n const providerRequest = { model, input: request.input, questions: request.questions }\n response = journal\n ? await journaledDecide(journal, decisions, providerRequest, signal)\n : await raceAbort(decisions.decide(providerRequest, signal), signal)\n }\n catch (err) {\n if (timeoutController.signal.aborted)\n return fail('timeout', `Decision timed out after ${timeoutMs}ms`, { ...who, error: asError(err) })\n if (signal.aborted)\n return fail('aborted', 'Decision was cancelled', { ...who, error: asError(err) })\n const classified = classifyDecisionError(err)\n if (classified.modelUnavailable)\n unavailableModels.add(modelKey(provider, model))\n return fail(classified.reason, classified.message, { ...who, error: asError(err) })\n }\n finally {\n clearTimeout(timer)\n }\n\n const answerProblem = validateDecisionAnswers(request.questions, response.answers)\n if (answerProblem)\n return fail('invalid', `Malformed decision response: ${answerProblem}`, who)\n\n const answers = Object.fromEntries(Object.keys(request.questions).map(name => [name, response.answers[name]]))\n const end: DecisionEndHookContext = {\n ...started,\n model: response.model || model,\n durationMs: Date.now() - startedAt,\n ok: true,\n usage: response.usage,\n answers,\n }\n await safeCallHook('decision:end', end)\n return {\n ok: true,\n decisionId,\n answers: answers as DecisionAnswers<DecisionQuestions>,\n usage: response.usage,\n provider: provider.name,\n model: response.model || model,\n ...(response.id ? { id: response.id } : {}),\n }\n }\n\n function forRun(runId: string | undefined, runSignal?: AbortSignal, journal?: DecisionJournal, newId: () => string = randomUUID): AgentDecide {\n const decide = async (request: DecisionRequest, opts?: { throw: true }): Promise<DecisionOutcome> => {\n const decisionId = `dec_${newId()}`\n let outcome: DecisionOutcome\n try {\n outcome = await run(runId, runSignal, request, journal, decisionId)\n }\n catch (err) {\n // Defensive: `run` handles every known failure; anything reaching here is a bug.\n outcome = { ok: false, decisionId, reason: 'error', message: asError(err).message, error: asError(err) }\n }\n if (opts?.throw && !outcome.ok)\n throw new DecisionError(outcome)\n return outcome\n }\n Object.defineProperty(decide, 'available', { get: isAvailable, enumerable: true })\n return decide as unknown as AgentDecide\n }\n\n return { forRun }\n}\n\nfunction asError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err))\n}\n\n/** Reject as soon as `signal` aborts, even if the provider ignores it. */\nfunction raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const onAbort = (): void => reject(abortError(signal))\n if (signal.aborted)\n return onAbort()\n signal.addEventListener('abort', onAbort, { once: true })\n promise.then(\n (v) => {\n signal.removeEventListener('abort', onAbort)\n resolve(v)\n },\n (e) => {\n signal.removeEventListener('abort', onAbort)\n reject(e)\n },\n )\n })\n}\n\n/**\n * The provider call of a journaled decision, failures included: they are\n * recorded as plain JSON and rethrown, so a replayed decision sees the same\n * outcome and takes the same branch. A timeout or abort is recorded as such\n * even when the provider resolved anyway: the caller already gave up.\n */\nasync function journaledDecide(journal: DecisionJournal, decisions: ProviderDecisions, request: ProviderDecisionRequest, signal: AbortSignal): Promise<ProviderDecisionResponse> {\n const entry = await journal<JournaledDecision>(async () => {\n try {\n // Inside the step: a key present live but missing on replay must not change the outcome.\n const availability = decisions.availability()\n if (!availability.available)\n throw new DecisionUnavailableError(availability.reason)\n const response = await raceAbort(decisions.decide(request, signal), signal)\n return signal.aborted\n ? { ok: false, error: serializeDecisionError(abortError(signal)) }\n : { ok: true, response }\n }\n catch (err) {\n return { ok: false, error: serializeDecisionError(signal.aborted ? abortError(signal) : err) }\n }\n })\n if (!entry.ok)\n throw deserializeDecisionError(entry.error)\n return entry.response\n}\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.js";
2
2
  import { p as errorMessage } from "./errors-C3z7pmOx.js";
3
- import { Ht as isSettingsIcon, Lt as looksLikeDefinition } from "./registry-B9INJwJR.js";
3
+ import { Bt as looksLikeDefinition, Gt as isSettingsIcon } from "./registry-CnnKaY9s.js";
4
4
  import { t as createDebugLog } from "./debug-log-DiNRNfzw.js";
5
5
  import { t as projectUserPaths } from "./project-user-paths-Chsi83mp.js";
6
- import { t as checkZidaneVersion } from "./host-version-CNAr9pov.js";
6
+ import { t as checkZidaneVersion } from "./host-version-BIx4CuBM.js";
7
7
  import { r as createLogger, t as consoleSink } from "./logger-BQj8QCJp.js";
8
8
  import { a as registerExtensionApiHostModules, s as resolveExtensionHostModules } from "./host-modules-C2GGiSjg.js";
9
- import { a as runtimeLoadedExtension, t as NO_DEFINITION_ERROR } from "./loaded-entry-DTURJo_R.js";
9
+ import { a as runtimeLoadedExtension, t as NO_DEFINITION_ERROR } from "./loaded-entry-C0vkCVNQ.js";
10
10
  import { createRequire } from "node:module";
11
11
  import { basename, delimiter, dirname, join, resolve, sep } from "node:path";
12
12
  import { lstatSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
@@ -620,4 +620,4 @@ function isExtensionManifest(value) {
620
620
  //#endregion
621
621
  export { precompileExtension as _, discovery_exports as a, defineExtension as b, inlineLoadedExtension as c, parseExtensionScanPathEnv as d, readExtensionManifest as f, COMPILE_CACHE_MAX_AGE_MS as g, withTimeout as h, discoverExtensions as i, loadExtensionFolder as l, scanExtensionDirectories as m, defaultExtensionScanPaths as n, findEntryFile as o, releaseExtensionModules as p, definitionExport as r, importExtensionEntry as s, ENTRY_CANDIDATES as t, loadableExtensionDirs as u, pruneExtensionCompileCache as v, EXTENSION_API_VERSION as y };
622
622
 
623
- //# sourceMappingURL=discovery-DycXFxdA.js.map
623
+ //# sourceMappingURL=discovery-CX7nbMCn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery-DycXFxdA.js","names":[],"sources":["../src/extensions/api/define.ts","../src/extensions/compile-cache.ts","../src/extensions/with-timeout.ts","../src/extensions/discovery.ts"],"sourcesContent":["/**\n * Extension API: the definition an extension module exports.\n */\n\nimport type { ExtensionContext } from './context'\nimport type { Cleanup } from './core'\nimport type { SettingsSchema } from './settings'\n\n/** The API version this runtime supports. */\nexport const EXTENSION_API_VERSION = 1\n\n/**\n * An extension. Export it as the module's default export, either through\n * `defineExtension` or as a plain object (no import needed).\n *\n * @typeParam S - The settings schema, so `ctx.settings.get` is typed.\n */\nexport interface ExtensionDefinition<S extends SettingsSchema = SettingsSchema> {\n /** Must be `1`. */\n apiVersion: 1\n /** Lowercase id, words joined by `-`. Equals the folder name for discovered extensions. Prefixes every id the extension contributes. */\n id: string\n /** Name shown in Settings. Defaults to `id`. */\n displayName?: string\n /**\n * The extension's icon in each app's vocabulary: `gui` an icon class for\n * the desktop and web apps (a Phosphor class such as `'i-ph-git-branch'`),\n * `tui` a short glyph for the terminal app. The settings sidebar draws it\n * on the extension's entry. Absent: a letter badge of the display name.\n */\n icon?: { gui?: string, tui?: string }\n /** Semver of the extension. */\n version?: string\n /** One line shown in Settings. */\n description?: string\n /**\n * Other extensions this one builds on: `{ id: semver range }`. Sets the load\n * order and gives install hints. A missing dependency never fails a load;\n * `ctx.use` reports it as inactive.\n */\n dependencies?: Readonly<Record<string, string>>\n /** Settings the extension reads. Shown in Settings even when the extension is off. */\n settings?: S\n /** Entry exporting React components for the desktop and web apps. Default `ui.tsx` when present. */\n ui?: string\n /** Entry exporting OpenTUI React components for the terminal app. Default `tui.tsx` when present. */\n tui?: string\n /**\n * Runs once per load. Register what the extension adds. May return a\n * cleanup that runs when the extension unloads. Registrations made later\n * (after an await, from a handler) work until the extension unloads.\n */\n setup: (ctx: ExtensionContext<S>) => void | Cleanup | Promise<void | Cleanup>\n}\n\n/**\n * Returns `definition` unchanged. It anchors type inference for `ctx`.\n *\n * @param definition - The extension.\n * @returns The same `definition`.\n */\nexport function defineExtension<S extends SettingsSchema = SettingsSchema>(definition: ExtensionDefinition<S>): ExtensionDefinition<S> {\n return definition\n}\n","/**\n * Compiled extension code kept on disk, for Node hosts (the desktop app):\n * they load extensions through jiti, which compiles each TypeScript file\n * with Babel on the calling thread, tens of ms per file, on every load. Bun\n * hosts import natively and never use it.\n *\n * jiti's own file cache does the keeping: one entry per source path (an edit\n * rewrites it), valid only while it ends with the hash of the source it was\n * compiled from, so a changed file is compiled again, never served stale. A\n * load and {@link precompileExtension} write the same entries (the same\n * options, {@link extensionJitiOptions}), so a host can compile on another\n * thread ahead of a load, and the load only reads.\n *\n * Entries are written for files that change; an entry not rewritten in\n * {@link COMPILE_CACHE_MAX_AGE_MS} goes ({@link pruneExtensionCompileCache}),\n * and a file still in use compiles once more.\n */\nimport type { JitiOptions } from 'jiti'\nimport type { Dirent } from 'node:fs'\nimport { readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/** How old an entry may grow without being rewritten before {@link pruneExtensionCompileCache} drops it. */\nexport const COMPILE_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Files jiti always compiles: TypeScript. */\nconst COMPILED_FILE = /\\.[cm]?tsx?$/\n\n/**\n * jiti, imported by a name the Bun binary's bundler leaves alone: a Node host\n * resolves it from zidane's dependencies, the Bun binary never runs it.\n */\nexport async function loadJiti(): Promise<typeof import('jiti')> {\n const specifier = 'jiti'\n return import(specifier) as Promise<typeof import('jiti')>\n}\n\n/**\n * The jiti options every extension load and precompile share: the cache\n * entries' names depend on them, so both must use exactly these.\n *\n * @param compileCache - The cache folder; none: nothing is kept.\n */\nexport function extensionJitiOptions(compileCache: string | undefined): JitiOptions {\n return { moduleCache: true, fsCache: compileCache ?? false, interopDefault: false }\n}\n\n/**\n * The names jiti keeps a file under: the path a load is given (the entry, by\n * the scanned folder), and the real path Node's resolver gives the files it\n * imports. They differ only below a symlinked folder (macOS's `/var`).\n */\nfunction cacheNames(path: string): string[] {\n try {\n const real = realpathSync(path)\n return real === path ? [path] : [path, real]\n }\n catch {\n return [path]\n }\n}\n\n/** An extension folder's TypeScript files, `node_modules` and dot-folders left out. */\nfunction compiledFiles(dir: string): string[] {\n const files: string[] = []\n const walk = (folder: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(folder, { withFileTypes: true })\n }\n catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.'))\n continue\n const path = join(folder, entry.name)\n if (entry.isDirectory())\n walk(path)\n else if (COMPILED_FILE.test(entry.name))\n files.push(path)\n }\n }\n walk(dir)\n return files\n}\n\n/**\n * Compiles an extension folder's TypeScript into the cache, as a load would.\n * Files whose entry is current are only checked. A file that does not compile\n * is skipped (the load reports it).\n *\n * @param dir - The extension folder.\n * @param compileCache - The cache folder.\n * @returns The files checked or compiled.\n */\nexport async function precompileExtension(dir: string, compileCache: string): Promise<number> {\n const { createJiti } = await loadJiti()\n const jiti = createJiti(dir, extensionJitiOptions(compileCache))\n const files = compiledFiles(dir)\n for (const file of files) {\n let source: string\n try {\n source = readFileSync(file, 'utf8')\n }\n catch {\n continue\n }\n for (const filename of cacheNames(file)) {\n try {\n // What a load passes for a module it imports (`async`): the same entry name and content.\n jiti.transform({ filename, source, ts: true, async: true, jsx: jiti.options.jsx })\n }\n catch {\n // Not compilable: the load says why.\n }\n }\n }\n return files.length\n}\n\n/**\n * Drops the entries not rewritten in `maxAgeMs`.\n *\n * @param compileCache - The cache folder.\n * @param maxAgeMs - The age past which an entry goes.\n * @param now - The current time.\n * @returns The entries dropped.\n */\nexport function pruneExtensionCompileCache(compileCache: string, maxAgeMs = COMPILE_CACHE_MAX_AGE_MS, now = Date.now()): number {\n let names: string[]\n try {\n names = readdirSync(compileCache)\n }\n catch {\n return 0\n }\n let dropped = 0\n for (const name of names) {\n const path = join(compileCache, name)\n try {\n if (now - statSync(path).mtimeMs <= maxAgeMs)\n continue\n rmSync(path, { force: true })\n dropped += 1\n }\n catch {\n // Gone meanwhile.\n }\n }\n return dropped\n}\n","/**\n * Internal: race a promise against a timeout. Shared by the discovery\n * loader (module imports) and the registry builder (extension `setup()`).\n * The timer is always cleared so a settled promise doesn't pin the\n * event loop for the full timeout.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n onTimeout?: () => void,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n onTimeout?.()\n reject(new Error(message))\n }, timeoutMs)\n })\n return Promise.race([promise, timeout]).finally(() => {\n if (timer)\n clearTimeout(timer)\n })\n}\n","/**\n * Extension discovery.\n *\n * Walks the standard project + user roots\n * (`{project,user}/.{agents,zidane}/extensions/<name>/`) using the\n * shared {@link projectUserPaths} convention, then dynamic-imports only\n * trusted `<name>/index.{ts,tsx,mjs,js}` files (probed in\n * that preference order — see {@link ENTRY_CANDIDATES}). The first valid\n * module per name wins — project beats user, `.agents/` beats\n * `.<prefix>/` within a single scope (see `projectUserPaths` for the\n * full order).\n *\n * Loading uses native `await import(pathToFileURL(...))` under Bun, which\n * handles `.ts`/`.tsx` and JS variants in `bun run` and in\n * `bun build --compile` binaries; Node hosts load through jiti (see\n * {@link importExtensionEntry}).\n *\n * Before each import the extension directory's module graph is EVICTED\n * from the module cache (`require.cache`: Bun's covers ESM too, jiti's\n * Node graph lives there), so\n * re-running discovery — the `/reload` command, a cwd-change rebuild, an\n * overwrite-install — loads the current on-disk code, deep imports\n * included. Module-level state resets per reload; the teardown contract\n * covers cleanup of the outgoing instance.\n *\n * Failures are isolation-first: a broken module produces a\n * {@link LoadedExtension} with `error` set instead of throwing — one\n * vibe-coded syntax error mustn't keep the TUI from booting.\n */\n\nimport type { LogSink } from '../logger'\nimport type { ExtensionDefinition } from './api/define'\nimport type { ExtensionManifest, LoadedExtension, LoadedExtensionSource } from './types'\nimport { lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, delimiter, dirname, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { createDebugLog } from '../debug-log'\nimport { errorMessage } from '../errors'\nimport { consoleSink, createLogger } from '../logger'\nimport { EXTENSION_API_VERSION } from './api/define'\nimport { extensionJitiOptions, loadJiti } from './compile-cache'\nimport { registerExtensionApiHostModules, resolveExtensionHostModules } from './host-modules'\nimport { checkZidaneVersion } from './host-version'\nimport { NO_DEFINITION_ERROR, runtimeLoadedExtension } from './runtime/loaded-entry'\nimport { looksLikeDefinition } from './runtime/validate-definition'\nimport { isSettingsIcon } from './settings/validate'\nimport { withTimeout } from './with-timeout'\n\n/**\n * Scoped `ZIDANE_DEBUG` logger. A `const` rather than a hoisted function\n * declaration, so it must precede its call sites.\n */\nconst debugLog = createDebugLog('extensions')\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Entry-point filenames probed inside each `<name>/` directory, in\n * preference order. Bun loads `.ts` / `.tsx` natively; `.mjs` / `.js`\n * are accepted so pre-compiled extensions (or vibe-coded JS-only ones)\n * work too. `index.js` is last because most authors will reach for\n * `.ts` first.\n */\nconst ENTRY_CANDIDATES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/** A discovered (but not yet loaded) extension entry. */\ninterface ExtensionScanEntry {\n /** Folder name — the canonical persisted extension identity. */\n name: string\n /** Absolute path to the extension directory. */\n dir: string\n /** Absolute path to the entry file picked from `ENTRY_CANDIDATES`. */\n entry: string\n /** Manifest read from the extension, when it has a valid one. */\n manifest?: ExtensionManifest\n /** Where the discovery root lives. */\n source: LoadedExtensionSource\n}\n\ninterface ExtensionScanRoot {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * One extension folder a config entry links in place (a local `source` in\n * `config.json`), scanned ahead of every root. Its scope gates it like a\n * scan root's: a `project` link loads only in a trusted project.\n */\nexport interface LinkedExtensionDir {\n /** Absolute extension folder. */\n readonly dir: string\n /** The config file that links it. */\n readonly source: 'project' | 'user'\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Default scan roots for extension discovery. Mirrors\n * {@link defaultSkillScanPaths} / {@link defaultMcpsConfigPaths}; the\n * subPath is `'extensions'` so each root expands to\n * `<scope>/.<convention>/extensions/`.\n *\n * @param opts - Overrides for the working directory (`cwd`), home directory (`home`) and storage prefix (`prefix`).\n */\nexport function defaultExtensionScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): ExtensionScanRoot[] {\n return projectUserPaths({ subPath: 'extensions', ...opts })\n}\n\n/**\n * Parse a platform-delimited list of additional extension scan roots.\n * Useful for local dogfooding / host-provided packages:\n *\n * ZIDANE_EXTENSION_DIRS=/repo/extensions bun run tui\n *\n * Each path is a scan ROOT whose children are extension dirs/packages\n * (`<root>/<name>/index.ts`), not a single extension directory.\n *\n * @param value - Delimited list of scan roots. Default: `ZIDANE_EXTENSION_DIRS`.\n */\nexport function parseExtensionScanPathEnv(value: string | undefined = process.env.ZIDANE_EXTENSION_DIRS): string[] {\n if (!value)\n return []\n return value.split(delimiter).map(p => p.trim()).filter(Boolean)\n}\n\n/**\n * Walk every scan root, collecting one {@link ExtensionScanEntry} per\n * `<root>/<name>/index.*` hit. First-found per `name` wins (matches the\n * project-user precedence used elsewhere — see `projectUserPaths` JSDoc).\n *\n * Non-existent roots are skipped silently. Stat errors inside a present\n * root are surfaced under `ZIDANE_DEBUG` only — they shouldn't bring\n * boot to a halt.\n *\n * @param opts - Overrides for `cwd`, `home` and `prefix`, extra scan roots in `extensionScanPaths`, and config-linked folders in `linkedExtensionDirs` (scanned first).\n */\nexport function scanExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const seen = new Map<string, ExtensionScanEntry>()\n for (const entry of scanAllExtensionDirectories(opts)) {\n if (!seen.has(entry.name))\n seen.set(entry.name, entry)\n }\n return [...seen.values()]\n}\n\nfunction scanAllExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const roots: ExtensionScanRoot[] = [\n ...(opts.extensionScanPaths ?? []).map(path => ({ path: resolve(path), source: 'user' as const })),\n ...defaultExtensionScanPaths(opts),\n ]\n const entriesOut: ExtensionScanEntry[] = []\n\n // Linked folders were named one by one in a config file: no root containment to check.\n for (const link of opts.linkedExtensionDirs ?? []) {\n const dir = resolve(link.dir)\n const entry = findEntryFile(dir)\n if (entry)\n entriesOut.push({ name: basename(dir), dir, entry, source: link.source, ...readExtensionManifest(dir) })\n }\n\n for (const root of roots) {\n try {\n // The scan root and its parent (`.zidane` / `.agents`) are part of\n // the trust boundary too — a symlinked parent would let every entry\n // resolve outside the configured roots while the per-entry lstat\n // checks below still pass.\n if (lstatSync(dirname(root.path)).isSymbolicLink())\n continue\n if (!lstatSync(root.path).isDirectory())\n continue\n }\n catch {\n continue\n }\n let realRoot: string\n try {\n realRoot = realpathSync(root.path)\n }\n catch (err) {\n debugLog(`realpath ${root.path}`, err)\n continue\n }\n let entries: string[]\n try {\n // readdir order is the filesystem's (sorted on APFS, hash or creation\n // order on ext4 / tmpfs); load order must not depend on it.\n entries = readdirSync(root.path).sort()\n }\n catch (err) {\n debugLog(`readdir ${root.path}`, err)\n continue\n }\n for (const name of entries) {\n if (name.startsWith('.'))\n continue\n const dir = resolve(root.path, name)\n try {\n // Extension roots and entry files are trust boundaries. Do not follow\n // symlinks out of the configured scan roots.\n if (!lstatSync(dir).isDirectory())\n continue\n }\n catch (err) {\n debugLog(`stat ${dir}`, err)\n continue\n }\n const entry = findEntryFile(dir)\n if (!entry)\n continue\n // Containment: the canonical entry file must stay under the\n // canonical scan root. Catches symlinks at any level the lstat\n // checks above didn't cover.\n try {\n const realEntry = realpathSync(entry)\n if (!realEntry.startsWith(realRoot + sep)) {\n debugLog(`skip ${entry}`, new Error(`resolves outside scan root ${realRoot}`))\n continue\n }\n }\n catch (err) {\n debugLog(`realpath ${entry}`, err)\n continue\n }\n entriesOut.push({ name, dir, entry, source: root.source, ...readExtensionManifest(dir) })\n }\n }\n return entriesOut\n}\n\n/**\n * Pick the first existing `index.*` file inside `dir`, or `null` if none.\n * Exported for the installer, which probes fetched trees with the same rule.\n */\nexport function findEntryFile(dir: string): string | null {\n for (const candidate of ENTRY_CANDIDATES) {\n const full = resolve(dir, candidate)\n try {\n if (lstatSync(full).isFile())\n return full\n }\n catch {\n continue\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Loading\n// ---------------------------------------------------------------------------\n\n/**\n * Discover + load every trusted extension reachable from the default\n * scan paths — including ones the user's enable allowlist has toggled\n * OFF. Disabled extensions still import (and later run `setup()`) so\n * their config schema is browsable in Settings before enabling; the\n * allowlist gates their CONTRIBUTIONS at registry level, not their code.\n * The trust boundary stays with `trustProjectExtensions`. Returns one {@link LoadedExtension} per scan entry:\n * skipped entries have no definition, failed entries carry `error`.\n * Loaders never throw — the host can decide what to do with load errors\n * (typically: ship them to the Settings panel).\n *\n * Imports run in parallel: a slow extension can't block the rest. The\n * returned array preserves the original scan order (project-first,\n * then user) so first-found-wins precedence stays intact.\n *\n * `signal` is honored opportunistically — if the abort fires BEFORE\n * we dispatch imports, the whole set is skipped. Once `Promise.all` is\n * in flight, individual imports can't be cancelled (the module system\n * has no abort affordance).\n *\n * Project-scoped extensions execute arbitrary local code, so they're\n * skipped unless `trustProjectExtensions` resolves to `true` — either\n * via the option or the `ZIDANE_TRUST_PROJECT_EXTENSIONS=1` env var\n * fallback. User-scoped extensions always load.\n *\n * Each extension's module graph is evicted from the runtime cache\n * before import, so calling this again (e.g. `/reload`) re-evaluates\n * the current on-disk code — edits, overwrites, and updates apply\n * without a restart.\n *\n * @param opts - Scan overrides (`cwd`, `home`, `prefix`, `extensionScanPaths`), an abort `signal`, `trustProjectExtensions`, `importTimeoutMs` (default 10000) as the per-extension import limit, and the `sink` non-blocking load warnings go to (default: `warn`+ to the console, like `buildExtensionRegistry`).\n */\nexport async function discoverExtensions(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n signal?: AbortSignal\n trustProjectExtensions?: boolean\n importTimeoutMs?: number\n sink?: LogSink\n /** Node hosts: the folder compiled extension code is kept in (`./compile-cache`). */\n compileCache?: string\n} = {}): Promise<LoadedExtension[]> {\n const entries = selectExtensionEntries(scanAllExtensionDirectories(opts), opts)\n if (opts.signal?.aborted || entries.length === 0)\n return []\n const sink = opts.sink ?? consoleSink({ minLevel: 'warn' })\n return Promise.all(entries.map((entry) => {\n if (!('entry' in entry))\n return Promise.resolve(entry)\n return loadExtensionFromDisk(entry, opts.importTimeoutMs ?? 10_000, sink, opts.compileCache)\n }))\n}\n\n/**\n * The folders {@link discoverExtensions} would import for the same options:\n * a trusted extension's, never an untrusted project's. What a host compiles\n * ahead of a load (`precompileExtension`).\n *\n * @param opts - Scan overrides and `trustProjectExtensions`, as for {@link discoverExtensions}.\n */\nexport function loadableExtensionDirs(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n trustProjectExtensions?: boolean\n} = {}): string[] {\n return selectExtensionEntries(scanAllExtensionDirectories(opts), opts).flatMap(entry => ('entry' in entry ? [entry.dir] : []))\n}\n\nfunction selectExtensionEntries(\n entries: readonly ExtensionScanEntry[],\n opts: { trustProjectExtensions?: boolean },\n): Array<ExtensionScanEntry | LoadedExtension> {\n // Safe by default: project extensions are untrusted unless the host\n // opts in explicitly or the user sets the env var the error message\n // below points at.\n const trustProjectExtensions = opts.trustProjectExtensions\n ?? (process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === '1')\n const selected = new Map<string, ExtensionScanEntry | LoadedExtension>()\n const locked = new Set<string>()\n\n for (const entry of entries) {\n if (locked.has(entry.name))\n continue\n if (entry.source === 'project' && !trustProjectExtensions) {\n if (!selected.has(entry.name)) {\n selected.set(entry.name, {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: 'Project extensions are disabled until this project is trusted. Set ZIDANE_TRUST_PROJECT_EXTENSIONS=1 or pass trustProjectExtensions to enable them.',\n })\n }\n continue\n }\n selected.set(entry.name, entry)\n locked.add(entry.name)\n }\n\n return [...selected.values()]\n}\n\nconst requireForCache = createRequire(import.meta.url)\n\n/**\n * Bun only: monotonic import generation, appended as a query so each\n * discovery pass gets a fresh ENTRY module. The query alone is not enough: relative\n * imports resolve to plain file paths and hit the cache, which is why\n * {@link evictModuleGraph} runs first. Both together are what makes\n * `/reload` a true hot-reload (verified empirically: query-only keeps\n * deep imports stale; eviction-only keeps the entry stale).\n */\nlet importGeneration = 0\n\n/**\n * Evict everything under `dir` from the module cache. Under Bun,\n * `require.cache` deletion covers the ES registry for these keys and the\n * entry itself is refreshed by the generation query above; under Node,\n * jiti keeps the whole graph there, entry included, so eviction alone\n * refreshes it. The extension's own `node_modules` are evicted\n * too so a dependency bump applies on update without a restart.\n */\nfunction evictModuleGraph(dir: string): void {\n const cache = requireForCache.cache\n if (!cache)\n return\n // Cache keys are PHYSICAL paths — realpath the dir or the prefix match\n // silently misses through symlinks (macOS `/var` → `/private/var`).\n let physical: string\n try {\n physical = realpathSync(dir)\n }\n catch {\n physical = dir\n }\n for (const root of physical === dir ? [dir] : [dir, physical]) {\n const prefix = root.endsWith(sep) ? root : root + sep\n for (const key of Object.keys(cache)) {\n if (key.startsWith(prefix))\n delete cache[key]\n }\n }\n}\n\n/**\n * Import an extension entry fresh from disk, its relative import graph\n * included. Every import of an extension goes through here (discovery and\n * install validation), so both runtimes get the same reload semantics.\n *\n * Bun: native `import()` — `.ts`/`.tsx`, extensionless relative imports\n * and host modules (`Bun.plugin`) all resolve natively.\n *\n * Node (the Electron GUI): jiti, NOT native `import()`. Node's ESM cache\n * can't be evicted, so a cache-busting query per reload would pin every\n * generation's module graph for the life of the process. jiti evaluates\n * the graph as CJS into `require.cache`, which {@link evictModuleGraph}\n * clears — old generations become collectible once their registry is\n * disposed (asserted by test/extensions/node-reload.test.ts). Stable URLs\n * also keep the cache flat. jiti additionally gives Node full TS/TSX and\n * `./foo` → `foo.ts` resolution, and maps the host's modules\n * (`'zidane'`, …) through `virtualModules` to the LIVE instances.\n * With a `compileCache` folder, compiled files are kept there and only a\n * changed one is compiled again (`./compile-cache`); without one, every\n * load compiles.\n *\n * @param entryPath - The extension's entry file.\n * @param dir - The extension's folder, whose module graph is evicted first.\n * @param options - `compileCache`: Node hosts' compile cache folder.\n * @param options.compileCache - The folder compiled code is kept in.\n */\nexport async function importExtensionEntry(entryPath: string, dir: string, options: { compileCache?: string } = {}): Promise<unknown> {\n // Every import gets the host's zidane, whatever ran before it in this process (an install's check runs\n // where nothing may have loaded extensions yet). Idempotent and lazy.\n registerExtensionApiHostModules()\n evictModuleGraph(dir)\n if (typeof Bun !== 'undefined') {\n importGeneration += 1\n return import(`${pathToFileURL(entryPath).href}?zidane-gen=${importGeneration}`)\n }\n const [{ createJiti }, virtualModules] = await Promise.all([loadJiti(), resolveExtensionHostModules()])\n const jiti = createJiti(entryPath, { ...extensionJitiOptions(options.compileCache), virtualModules })\n return jiti.import(entryPath)\n}\n\n/**\n * Drop an extension directory's modules from the cache without importing\n * again — for a one-off import whose directory is about to move or be\n * deleted (install validation), so nothing stays pinned under that path.\n */\nexport function releaseExtensionModules(dir: string): void {\n evictModuleGraph(dir)\n}\n\n/**\n * Import + validate a single extension entry. Always returns a\n * {@link LoadedExtension} — the load error (if any) lives on the\n * returned object, never as a thrown exception.\n */\nasync function loadExtensionFromDisk(entry: ExtensionScanEntry, timeoutMs: number, sink: LogSink, compileCache?: string): Promise<LoadedExtension> {\n // Before the import: a module written against a newer zidane would fail on\n // a missing export, which says nothing about the actual problem.\n const version = checkZidaneVersion(entry.dir)\n if (version?.level === 'error') {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: version.message,\n }\n }\n const failed = (error: string): LoadedExtension => ({\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error,\n })\n const manifestVersion = entry.manifest?.apiVersion\n if (manifestVersion !== undefined && manifestVersion !== EXTENSION_API_VERSION)\n return failed(`Unsupported extension API version \"${manifestVersion}\". This runtime supports apiVersion 1.`)\n if (entry.manifest?.name && entry.manifest.name !== entry.name)\n return failed(`Extension manifest name mismatch: folder is \"${entry.name}\" but manifest declares \"${entry.manifest.name}\".`)\n if (version)\n createLogger(sink, { extension: entry.name }).warn(version.message)\n try {\n const mod = await withTimeout(\n importExtensionEntry(entry.entry, entry.dir, compileCache !== undefined ? { compileCache } : {}),\n timeoutMs,\n `Timed out importing ${entry.entry} after ${timeoutMs}ms. The module's top-level code may still be running; an import can't be cancelled.`,\n )\n const where = { name: entry.name, dir: entry.dir, source: entry.source, ...(entry.manifest ? { manifest: entry.manifest } : {}) }\n const exported = moduleExports(mod)\n const definition = definitionExport(exported)\n if (definition !== undefined)\n return runtimeLoadedExtension(definition, where)\n return failed(`Module at ${entry.entry} does not export an extension definition (expected \\`default\\` or named \\`extension\\` with \\`apiVersion: 1\\`).`)\n }\n catch (err) {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: `Failed to import ${entry.entry}: ${errorMessage(err)}`,\n }\n }\n}\n\n/**\n * Imports one extension folder fresh from disk, as discovery would (version\n * check, manifest, module graph evicted first). Never rejects: a folder with\n * no entry file or a module that fails to import comes back with `error`.\n * The caller decides whether the folder may load at all (trust, precedence):\n * {@link loadableExtensionDirs} answers that.\n *\n * @param dir - The extension folder.\n * @param opts - Its source, the import bound (default 10 s), the warning sink and Node's compile cache.\n * @param opts.source - Where the folder was found.\n * @param opts.importTimeoutMs - How long the import may take.\n * @param opts.sink - Where load warnings go. Default: warnings to stderr.\n * @param opts.compileCache - Node hosts: the folder compiled code is kept in.\n * @returns The loaded entry, or one carrying the error.\n */\nexport async function loadExtensionFolder(dir: string, opts: { source: LoadedExtensionSource, importTimeoutMs?: number, sink?: LogSink, compileCache?: string }): Promise<LoadedExtension> {\n const name = basename(dir)\n const entry = findEntryFile(dir)\n const manifest = readExtensionManifest(dir)\n if (!entry)\n return { name, dir, source: opts.source, ...manifest, error: `No entry file (${ENTRY_CANDIDATES.join(', ')}) in ${dir}.` }\n return loadExtensionFromDisk({ name, dir, entry, source: opts.source, ...manifest }, opts.importTimeoutMs ?? 10_000, opts.sink ?? consoleSink({ minLevel: 'warn' }), opts.compileCache)\n}\n\n/**\n * The export the runtime validates: one claiming `apiVersion: 1`, else the\n * first object (its validation then says what is missing).\n *\n * @param exported - A module's `default` and `extension` exports.\n * @returns The export, or `undefined` when none is an object.\n */\nexport function definitionExport(exported: readonly unknown[]): unknown {\n return exported.find(looksLikeDefinition) ?? exported.find(value => !!value && typeof value === 'object')\n}\n\n/** The `default` and `extension` exports of a module, in that order of preference. */\nfunction moduleExports(mod: unknown): unknown[] {\n if (!mod || typeof mod !== 'object')\n return []\n const m = mod as Record<string, unknown>\n return [m.default, m.extension].filter(value => value !== undefined)\n}\n\n/**\n * Tag an inline (host-passed) extension into the same shape disk-\n * loaded ones produce, so host-bundled extensions (e.g. those passed\n * via `RunTuiOptions.extensions`) flow through `buildExtensionRegistry`\n * identically to disk-discovered ones. The runtime validates the\n * definition and lists it inactive with what is wrong.\n *\n * @param definition - Extension definition the host built in code.\n */\nexport function inlineLoadedExtension(definition: Pick<ExtensionDefinition, 'apiVersion'> | object): LoadedExtension {\n if (definition && typeof definition === 'object')\n return runtimeLoadedExtension(definition, { source: 'inline' })\n return { name: 'unknown', source: 'inline', error: NO_DEFINITION_ERROR }\n}\n\n/**\n * Read `extension.json` (or `package.json#zidane`) as a shape-validated\n * manifest. Exported for the installer so \"what counts as a manifest\" has\n * exactly one definition — a malformed manifest is ignored identically at\n * install-validation time and at discovery time.\n */\nexport function readExtensionManifest(dir: string): { manifest?: ExtensionManifest } {\n const fromExtensionJson = readJson(resolve(dir, 'extension.json'))\n if (isExtensionManifest(fromExtensionJson))\n return { manifest: fromExtensionJson }\n const pkg = readJson(resolve(dir, 'package.json'))\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n const manifest = (pkg as { zidane?: unknown }).zidane\n if (isExtensionManifest(manifest))\n return { manifest }\n }\n return {}\n}\n\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch {\n return null\n }\n}\n\nfunction isExtensionManifest(value: unknown): value is ExtensionManifest {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const v = value as Record<string, unknown>\n // `apiVersion`, when present, must be a number. We still recognize an\n // unsupported version (e.g. 1) as a manifest so the loader can surface a\n // clear \"unsupported API version\" error rather than silently ignoring it —\n // but the rest of the shape is still validated.\n if (v.apiVersion !== undefined && typeof v.apiVersion !== 'number')\n return false\n return (v.name === undefined || typeof v.name === 'string')\n && (v.displayName === undefined || typeof v.displayName === 'string')\n && (v.description === undefined || typeof v.description === 'string')\n && (v.icon === undefined || isSettingsIcon(v.icon))\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/** Re-export for tests / advanced hosts that want to log the scan results before loading. */\nexport type { ExtensionScanEntry }\n/** Re-export so callers can read the canonical entry file list. */\nexport { ENTRY_CANDIDATES }\n"],"mappings":"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"}
1
+ {"version":3,"file":"discovery-CX7nbMCn.js","names":[],"sources":["../src/extensions/api/define.ts","../src/extensions/compile-cache.ts","../src/extensions/with-timeout.ts","../src/extensions/discovery.ts"],"sourcesContent":["/**\n * Extension API: the definition an extension module exports.\n */\n\nimport type { ExtensionContext } from './context'\nimport type { Cleanup } from './core'\nimport type { SettingsSchema } from './settings'\n\n/** The API version this runtime supports. */\nexport const EXTENSION_API_VERSION = 1\n\n/**\n * An extension. Export it as the module's default export, either through\n * `defineExtension` or as a plain object (no import needed).\n *\n * @typeParam S - The settings schema, so `ctx.settings.get` is typed.\n */\nexport interface ExtensionDefinition<S extends SettingsSchema = SettingsSchema> {\n /** Must be `1`. */\n apiVersion: 1\n /** Lowercase id, words joined by `-`. Equals the folder name for discovered extensions. Prefixes every id the extension contributes. */\n id: string\n /** Name shown in Settings. Defaults to `id`. */\n displayName?: string\n /**\n * The extension's icon in each app's vocabulary: `gui` an icon class for\n * the desktop and web apps (a Phosphor class such as `'i-ph-git-branch'`),\n * `tui` a short glyph for the terminal app. The settings sidebar draws it\n * on the extension's entry. Absent: a letter badge of the display name.\n */\n icon?: { gui?: string, tui?: string }\n /** Semver of the extension. */\n version?: string\n /** One line shown in Settings. */\n description?: string\n /**\n * Other extensions this one builds on: `{ id: semver range }`. Sets the load\n * order and gives install hints. A missing dependency never fails a load;\n * `ctx.use` reports it as inactive.\n */\n dependencies?: Readonly<Record<string, string>>\n /** Settings the extension reads. Shown in Settings even when the extension is off. */\n settings?: S\n /** Entry exporting React components for the desktop and web apps. Default `ui.tsx` when present. */\n ui?: string\n /** Entry exporting OpenTUI React components for the terminal app. Default `tui.tsx` when present. */\n tui?: string\n /**\n * Runs once per load. Register what the extension adds. May return a\n * cleanup that runs when the extension unloads. Registrations made later\n * (after an await, from a handler) work until the extension unloads.\n */\n setup: (ctx: ExtensionContext<S>) => void | Cleanup | Promise<void | Cleanup>\n}\n\n/**\n * Returns `definition` unchanged. It anchors type inference for `ctx`.\n *\n * @param definition - The extension.\n * @returns The same `definition`.\n */\nexport function defineExtension<S extends SettingsSchema = SettingsSchema>(definition: ExtensionDefinition<S>): ExtensionDefinition<S> {\n return definition\n}\n","/**\n * Compiled extension code kept on disk, for Node hosts (the desktop app):\n * they load extensions through jiti, which compiles each TypeScript file\n * with Babel on the calling thread, tens of ms per file, on every load. Bun\n * hosts import natively and never use it.\n *\n * jiti's own file cache does the keeping: one entry per source path (an edit\n * rewrites it), valid only while it ends with the hash of the source it was\n * compiled from, so a changed file is compiled again, never served stale. A\n * load and {@link precompileExtension} write the same entries (the same\n * options, {@link extensionJitiOptions}), so a host can compile on another\n * thread ahead of a load, and the load only reads.\n *\n * Entries are written for files that change; an entry not rewritten in\n * {@link COMPILE_CACHE_MAX_AGE_MS} goes ({@link pruneExtensionCompileCache}),\n * and a file still in use compiles once more.\n */\nimport type { JitiOptions } from 'jiti'\nimport type { Dirent } from 'node:fs'\nimport { readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/** How old an entry may grow without being rewritten before {@link pruneExtensionCompileCache} drops it. */\nexport const COMPILE_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Files jiti always compiles: TypeScript. */\nconst COMPILED_FILE = /\\.[cm]?tsx?$/\n\n/**\n * jiti, imported by a name the Bun binary's bundler leaves alone: a Node host\n * resolves it from zidane's dependencies, the Bun binary never runs it.\n */\nexport async function loadJiti(): Promise<typeof import('jiti')> {\n const specifier = 'jiti'\n return import(specifier) as Promise<typeof import('jiti')>\n}\n\n/**\n * The jiti options every extension load and precompile share: the cache\n * entries' names depend on them, so both must use exactly these.\n *\n * @param compileCache - The cache folder; none: nothing is kept.\n */\nexport function extensionJitiOptions(compileCache: string | undefined): JitiOptions {\n return { moduleCache: true, fsCache: compileCache ?? false, interopDefault: false }\n}\n\n/**\n * The names jiti keeps a file under: the path a load is given (the entry, by\n * the scanned folder), and the real path Node's resolver gives the files it\n * imports. They differ only below a symlinked folder (macOS's `/var`).\n */\nfunction cacheNames(path: string): string[] {\n try {\n const real = realpathSync(path)\n return real === path ? [path] : [path, real]\n }\n catch {\n return [path]\n }\n}\n\n/** An extension folder's TypeScript files, `node_modules` and dot-folders left out. */\nfunction compiledFiles(dir: string): string[] {\n const files: string[] = []\n const walk = (folder: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(folder, { withFileTypes: true })\n }\n catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.'))\n continue\n const path = join(folder, entry.name)\n if (entry.isDirectory())\n walk(path)\n else if (COMPILED_FILE.test(entry.name))\n files.push(path)\n }\n }\n walk(dir)\n return files\n}\n\n/**\n * Compiles an extension folder's TypeScript into the cache, as a load would.\n * Files whose entry is current are only checked. A file that does not compile\n * is skipped (the load reports it).\n *\n * @param dir - The extension folder.\n * @param compileCache - The cache folder.\n * @returns The files checked or compiled.\n */\nexport async function precompileExtension(dir: string, compileCache: string): Promise<number> {\n const { createJiti } = await loadJiti()\n const jiti = createJiti(dir, extensionJitiOptions(compileCache))\n const files = compiledFiles(dir)\n for (const file of files) {\n let source: string\n try {\n source = readFileSync(file, 'utf8')\n }\n catch {\n continue\n }\n for (const filename of cacheNames(file)) {\n try {\n // What a load passes for a module it imports (`async`): the same entry name and content.\n jiti.transform({ filename, source, ts: true, async: true, jsx: jiti.options.jsx })\n }\n catch {\n // Not compilable: the load says why.\n }\n }\n }\n return files.length\n}\n\n/**\n * Drops the entries not rewritten in `maxAgeMs`.\n *\n * @param compileCache - The cache folder.\n * @param maxAgeMs - The age past which an entry goes.\n * @param now - The current time.\n * @returns The entries dropped.\n */\nexport function pruneExtensionCompileCache(compileCache: string, maxAgeMs = COMPILE_CACHE_MAX_AGE_MS, now = Date.now()): number {\n let names: string[]\n try {\n names = readdirSync(compileCache)\n }\n catch {\n return 0\n }\n let dropped = 0\n for (const name of names) {\n const path = join(compileCache, name)\n try {\n if (now - statSync(path).mtimeMs <= maxAgeMs)\n continue\n rmSync(path, { force: true })\n dropped += 1\n }\n catch {\n // Gone meanwhile.\n }\n }\n return dropped\n}\n","/**\n * Internal: race a promise against a timeout. Shared by the discovery\n * loader (module imports) and the registry builder (extension `setup()`).\n * The timer is always cleared so a settled promise doesn't pin the\n * event loop for the full timeout.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n onTimeout?: () => void,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n onTimeout?.()\n reject(new Error(message))\n }, timeoutMs)\n })\n return Promise.race([promise, timeout]).finally(() => {\n if (timer)\n clearTimeout(timer)\n })\n}\n","/**\n * Extension discovery.\n *\n * Walks the standard project + user roots\n * (`{project,user}/.{agents,zidane}/extensions/<name>/`) using the\n * shared {@link projectUserPaths} convention, then dynamic-imports only\n * trusted `<name>/index.{ts,tsx,mjs,js}` files (probed in\n * that preference order — see {@link ENTRY_CANDIDATES}). The first valid\n * module per name wins — project beats user, `.agents/` beats\n * `.<prefix>/` within a single scope (see `projectUserPaths` for the\n * full order).\n *\n * Loading uses native `await import(pathToFileURL(...))` under Bun, which\n * handles `.ts`/`.tsx` and JS variants in `bun run` and in\n * `bun build --compile` binaries; Node hosts load through jiti (see\n * {@link importExtensionEntry}).\n *\n * Before each import the extension directory's module graph is EVICTED\n * from the module cache (`require.cache`: Bun's covers ESM too, jiti's\n * Node graph lives there), so\n * re-running discovery — the `/reload` command, a cwd-change rebuild, an\n * overwrite-install — loads the current on-disk code, deep imports\n * included. Module-level state resets per reload; the teardown contract\n * covers cleanup of the outgoing instance.\n *\n * Failures are isolation-first: a broken module produces a\n * {@link LoadedExtension} with `error` set instead of throwing — one\n * vibe-coded syntax error mustn't keep the TUI from booting.\n */\n\nimport type { LogSink } from '../logger'\nimport type { ExtensionDefinition } from './api/define'\nimport type { ExtensionManifest, LoadedExtension, LoadedExtensionSource } from './types'\nimport { lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, delimiter, dirname, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { createDebugLog } from '../debug-log'\nimport { errorMessage } from '../errors'\nimport { consoleSink, createLogger } from '../logger'\nimport { EXTENSION_API_VERSION } from './api/define'\nimport { extensionJitiOptions, loadJiti } from './compile-cache'\nimport { registerExtensionApiHostModules, resolveExtensionHostModules } from './host-modules'\nimport { checkZidaneVersion } from './host-version'\nimport { NO_DEFINITION_ERROR, runtimeLoadedExtension } from './runtime/loaded-entry'\nimport { looksLikeDefinition } from './runtime/validate-definition'\nimport { isSettingsIcon } from './settings/validate'\nimport { withTimeout } from './with-timeout'\n\n/**\n * Scoped `ZIDANE_DEBUG` logger. A `const` rather than a hoisted function\n * declaration, so it must precede its call sites.\n */\nconst debugLog = createDebugLog('extensions')\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Entry-point filenames probed inside each `<name>/` directory, in\n * preference order. Bun loads `.ts` / `.tsx` natively; `.mjs` / `.js`\n * are accepted so pre-compiled extensions (or vibe-coded JS-only ones)\n * work too. `index.js` is last because most authors will reach for\n * `.ts` first.\n */\nconst ENTRY_CANDIDATES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/** A discovered (but not yet loaded) extension entry. */\ninterface ExtensionScanEntry {\n /** Folder name — the canonical persisted extension identity. */\n name: string\n /** Absolute path to the extension directory. */\n dir: string\n /** Absolute path to the entry file picked from `ENTRY_CANDIDATES`. */\n entry: string\n /** Manifest read from the extension, when it has a valid one. */\n manifest?: ExtensionManifest\n /** Where the discovery root lives. */\n source: LoadedExtensionSource\n}\n\ninterface ExtensionScanRoot {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * One extension folder a config entry links in place (a local `source` in\n * `config.json`), scanned ahead of every root. Its scope gates it like a\n * scan root's: a `project` link loads only in a trusted project.\n */\nexport interface LinkedExtensionDir {\n /** Absolute extension folder. */\n readonly dir: string\n /** The config file that links it. */\n readonly source: 'project' | 'user'\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Default scan roots for extension discovery. Mirrors\n * {@link defaultSkillScanPaths} / {@link defaultMcpsConfigPaths}; the\n * subPath is `'extensions'` so each root expands to\n * `<scope>/.<convention>/extensions/`.\n *\n * @param opts - Overrides for the working directory (`cwd`), home directory (`home`) and storage prefix (`prefix`).\n */\nexport function defaultExtensionScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): ExtensionScanRoot[] {\n return projectUserPaths({ subPath: 'extensions', ...opts })\n}\n\n/**\n * Parse a platform-delimited list of additional extension scan roots.\n * Useful for local dogfooding / host-provided packages:\n *\n * ZIDANE_EXTENSION_DIRS=/repo/extensions bun run tui\n *\n * Each path is a scan ROOT whose children are extension dirs/packages\n * (`<root>/<name>/index.ts`), not a single extension directory.\n *\n * @param value - Delimited list of scan roots. Default: `ZIDANE_EXTENSION_DIRS`.\n */\nexport function parseExtensionScanPathEnv(value: string | undefined = process.env.ZIDANE_EXTENSION_DIRS): string[] {\n if (!value)\n return []\n return value.split(delimiter).map(p => p.trim()).filter(Boolean)\n}\n\n/**\n * Walk every scan root, collecting one {@link ExtensionScanEntry} per\n * `<root>/<name>/index.*` hit. First-found per `name` wins (matches the\n * project-user precedence used elsewhere — see `projectUserPaths` JSDoc).\n *\n * Non-existent roots are skipped silently. Stat errors inside a present\n * root are surfaced under `ZIDANE_DEBUG` only — they shouldn't bring\n * boot to a halt.\n *\n * @param opts - Overrides for `cwd`, `home` and `prefix`, extra scan roots in `extensionScanPaths`, and config-linked folders in `linkedExtensionDirs` (scanned first).\n */\nexport function scanExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const seen = new Map<string, ExtensionScanEntry>()\n for (const entry of scanAllExtensionDirectories(opts)) {\n if (!seen.has(entry.name))\n seen.set(entry.name, entry)\n }\n return [...seen.values()]\n}\n\nfunction scanAllExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const roots: ExtensionScanRoot[] = [\n ...(opts.extensionScanPaths ?? []).map(path => ({ path: resolve(path), source: 'user' as const })),\n ...defaultExtensionScanPaths(opts),\n ]\n const entriesOut: ExtensionScanEntry[] = []\n\n // Linked folders were named one by one in a config file: no root containment to check.\n for (const link of opts.linkedExtensionDirs ?? []) {\n const dir = resolve(link.dir)\n const entry = findEntryFile(dir)\n if (entry)\n entriesOut.push({ name: basename(dir), dir, entry, source: link.source, ...readExtensionManifest(dir) })\n }\n\n for (const root of roots) {\n try {\n // The scan root and its parent (`.zidane` / `.agents`) are part of\n // the trust boundary too — a symlinked parent would let every entry\n // resolve outside the configured roots while the per-entry lstat\n // checks below still pass.\n if (lstatSync(dirname(root.path)).isSymbolicLink())\n continue\n if (!lstatSync(root.path).isDirectory())\n continue\n }\n catch {\n continue\n }\n let realRoot: string\n try {\n realRoot = realpathSync(root.path)\n }\n catch (err) {\n debugLog(`realpath ${root.path}`, err)\n continue\n }\n let entries: string[]\n try {\n // readdir order is the filesystem's (sorted on APFS, hash or creation\n // order on ext4 / tmpfs); load order must not depend on it.\n entries = readdirSync(root.path).sort()\n }\n catch (err) {\n debugLog(`readdir ${root.path}`, err)\n continue\n }\n for (const name of entries) {\n if (name.startsWith('.'))\n continue\n const dir = resolve(root.path, name)\n try {\n // Extension roots and entry files are trust boundaries. Do not follow\n // symlinks out of the configured scan roots.\n if (!lstatSync(dir).isDirectory())\n continue\n }\n catch (err) {\n debugLog(`stat ${dir}`, err)\n continue\n }\n const entry = findEntryFile(dir)\n if (!entry)\n continue\n // Containment: the canonical entry file must stay under the\n // canonical scan root. Catches symlinks at any level the lstat\n // checks above didn't cover.\n try {\n const realEntry = realpathSync(entry)\n if (!realEntry.startsWith(realRoot + sep)) {\n debugLog(`skip ${entry}`, new Error(`resolves outside scan root ${realRoot}`))\n continue\n }\n }\n catch (err) {\n debugLog(`realpath ${entry}`, err)\n continue\n }\n entriesOut.push({ name, dir, entry, source: root.source, ...readExtensionManifest(dir) })\n }\n }\n return entriesOut\n}\n\n/**\n * Pick the first existing `index.*` file inside `dir`, or `null` if none.\n * Exported for the installer, which probes fetched trees with the same rule.\n */\nexport function findEntryFile(dir: string): string | null {\n for (const candidate of ENTRY_CANDIDATES) {\n const full = resolve(dir, candidate)\n try {\n if (lstatSync(full).isFile())\n return full\n }\n catch {\n continue\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Loading\n// ---------------------------------------------------------------------------\n\n/**\n * Discover + load every trusted extension reachable from the default\n * scan paths — including ones the user's enable allowlist has toggled\n * OFF. Disabled extensions still import (and later run `setup()`) so\n * their config schema is browsable in Settings before enabling; the\n * allowlist gates their CONTRIBUTIONS at registry level, not their code.\n * The trust boundary stays with `trustProjectExtensions`. Returns one {@link LoadedExtension} per scan entry:\n * skipped entries have no definition, failed entries carry `error`.\n * Loaders never throw — the host can decide what to do with load errors\n * (typically: ship them to the Settings panel).\n *\n * Imports run in parallel: a slow extension can't block the rest. The\n * returned array preserves the original scan order (project-first,\n * then user) so first-found-wins precedence stays intact.\n *\n * `signal` is honored opportunistically — if the abort fires BEFORE\n * we dispatch imports, the whole set is skipped. Once `Promise.all` is\n * in flight, individual imports can't be cancelled (the module system\n * has no abort affordance).\n *\n * Project-scoped extensions execute arbitrary local code, so they're\n * skipped unless `trustProjectExtensions` resolves to `true` — either\n * via the option or the `ZIDANE_TRUST_PROJECT_EXTENSIONS=1` env var\n * fallback. User-scoped extensions always load.\n *\n * Each extension's module graph is evicted from the runtime cache\n * before import, so calling this again (e.g. `/reload`) re-evaluates\n * the current on-disk code — edits, overwrites, and updates apply\n * without a restart.\n *\n * @param opts - Scan overrides (`cwd`, `home`, `prefix`, `extensionScanPaths`), an abort `signal`, `trustProjectExtensions`, `importTimeoutMs` (default 10000) as the per-extension import limit, and the `sink` non-blocking load warnings go to (default: `warn`+ to the console, like `buildExtensionRegistry`).\n */\nexport async function discoverExtensions(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n signal?: AbortSignal\n trustProjectExtensions?: boolean\n importTimeoutMs?: number\n sink?: LogSink\n /** Node hosts: the folder compiled extension code is kept in (`./compile-cache`). */\n compileCache?: string\n} = {}): Promise<LoadedExtension[]> {\n const entries = selectExtensionEntries(scanAllExtensionDirectories(opts), opts)\n if (opts.signal?.aborted || entries.length === 0)\n return []\n const sink = opts.sink ?? consoleSink({ minLevel: 'warn' })\n return Promise.all(entries.map((entry) => {\n if (!('entry' in entry))\n return Promise.resolve(entry)\n return loadExtensionFromDisk(entry, opts.importTimeoutMs ?? 10_000, sink, opts.compileCache)\n }))\n}\n\n/**\n * The folders {@link discoverExtensions} would import for the same options:\n * a trusted extension's, never an untrusted project's. What a host compiles\n * ahead of a load (`precompileExtension`).\n *\n * @param opts - Scan overrides and `trustProjectExtensions`, as for {@link discoverExtensions}.\n */\nexport function loadableExtensionDirs(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n trustProjectExtensions?: boolean\n} = {}): string[] {\n return selectExtensionEntries(scanAllExtensionDirectories(opts), opts).flatMap(entry => ('entry' in entry ? [entry.dir] : []))\n}\n\nfunction selectExtensionEntries(\n entries: readonly ExtensionScanEntry[],\n opts: { trustProjectExtensions?: boolean },\n): Array<ExtensionScanEntry | LoadedExtension> {\n // Safe by default: project extensions are untrusted unless the host\n // opts in explicitly or the user sets the env var the error message\n // below points at.\n const trustProjectExtensions = opts.trustProjectExtensions\n ?? (process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === '1')\n const selected = new Map<string, ExtensionScanEntry | LoadedExtension>()\n const locked = new Set<string>()\n\n for (const entry of entries) {\n if (locked.has(entry.name))\n continue\n if (entry.source === 'project' && !trustProjectExtensions) {\n if (!selected.has(entry.name)) {\n selected.set(entry.name, {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: 'Project extensions are disabled until this project is trusted. Set ZIDANE_TRUST_PROJECT_EXTENSIONS=1 or pass trustProjectExtensions to enable them.',\n })\n }\n continue\n }\n selected.set(entry.name, entry)\n locked.add(entry.name)\n }\n\n return [...selected.values()]\n}\n\nconst requireForCache = createRequire(import.meta.url)\n\n/**\n * Bun only: monotonic import generation, appended as a query so each\n * discovery pass gets a fresh ENTRY module. The query alone is not enough: relative\n * imports resolve to plain file paths and hit the cache, which is why\n * {@link evictModuleGraph} runs first. Both together are what makes\n * `/reload` a true hot-reload (verified empirically: query-only keeps\n * deep imports stale; eviction-only keeps the entry stale).\n */\nlet importGeneration = 0\n\n/**\n * Evict everything under `dir` from the module cache. Under Bun,\n * `require.cache` deletion covers the ES registry for these keys and the\n * entry itself is refreshed by the generation query above; under Node,\n * jiti keeps the whole graph there, entry included, so eviction alone\n * refreshes it. The extension's own `node_modules` are evicted\n * too so a dependency bump applies on update without a restart.\n */\nfunction evictModuleGraph(dir: string): void {\n const cache = requireForCache.cache\n if (!cache)\n return\n // Cache keys are PHYSICAL paths — realpath the dir or the prefix match\n // silently misses through symlinks (macOS `/var` → `/private/var`).\n let physical: string\n try {\n physical = realpathSync(dir)\n }\n catch {\n physical = dir\n }\n for (const root of physical === dir ? [dir] : [dir, physical]) {\n const prefix = root.endsWith(sep) ? root : root + sep\n for (const key of Object.keys(cache)) {\n if (key.startsWith(prefix))\n delete cache[key]\n }\n }\n}\n\n/**\n * Import an extension entry fresh from disk, its relative import graph\n * included. Every import of an extension goes through here (discovery and\n * install validation), so both runtimes get the same reload semantics.\n *\n * Bun: native `import()` — `.ts`/`.tsx`, extensionless relative imports\n * and host modules (`Bun.plugin`) all resolve natively.\n *\n * Node (the Electron GUI): jiti, NOT native `import()`. Node's ESM cache\n * can't be evicted, so a cache-busting query per reload would pin every\n * generation's module graph for the life of the process. jiti evaluates\n * the graph as CJS into `require.cache`, which {@link evictModuleGraph}\n * clears — old generations become collectible once their registry is\n * disposed (asserted by test/extensions/node-reload.test.ts). Stable URLs\n * also keep the cache flat. jiti additionally gives Node full TS/TSX and\n * `./foo` → `foo.ts` resolution, and maps the host's modules\n * (`'zidane'`, …) through `virtualModules` to the LIVE instances.\n * With a `compileCache` folder, compiled files are kept there and only a\n * changed one is compiled again (`./compile-cache`); without one, every\n * load compiles.\n *\n * @param entryPath - The extension's entry file.\n * @param dir - The extension's folder, whose module graph is evicted first.\n * @param options - `compileCache`: Node hosts' compile cache folder.\n * @param options.compileCache - The folder compiled code is kept in.\n */\nexport async function importExtensionEntry(entryPath: string, dir: string, options: { compileCache?: string } = {}): Promise<unknown> {\n // Every import gets the host's zidane, whatever ran before it in this process (an install's check runs\n // where nothing may have loaded extensions yet). Idempotent and lazy.\n registerExtensionApiHostModules()\n evictModuleGraph(dir)\n if (typeof Bun !== 'undefined') {\n importGeneration += 1\n return import(`${pathToFileURL(entryPath).href}?zidane-gen=${importGeneration}`)\n }\n const [{ createJiti }, virtualModules] = await Promise.all([loadJiti(), resolveExtensionHostModules()])\n const jiti = createJiti(entryPath, { ...extensionJitiOptions(options.compileCache), virtualModules })\n return jiti.import(entryPath)\n}\n\n/**\n * Drop an extension directory's modules from the cache without importing\n * again — for a one-off import whose directory is about to move or be\n * deleted (install validation), so nothing stays pinned under that path.\n */\nexport function releaseExtensionModules(dir: string): void {\n evictModuleGraph(dir)\n}\n\n/**\n * Import + validate a single extension entry. Always returns a\n * {@link LoadedExtension} — the load error (if any) lives on the\n * returned object, never as a thrown exception.\n */\nasync function loadExtensionFromDisk(entry: ExtensionScanEntry, timeoutMs: number, sink: LogSink, compileCache?: string): Promise<LoadedExtension> {\n // Before the import: a module written against a newer zidane would fail on\n // a missing export, which says nothing about the actual problem.\n const version = checkZidaneVersion(entry.dir)\n if (version?.level === 'error') {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: version.message,\n }\n }\n const failed = (error: string): LoadedExtension => ({\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error,\n })\n const manifestVersion = entry.manifest?.apiVersion\n if (manifestVersion !== undefined && manifestVersion !== EXTENSION_API_VERSION)\n return failed(`Unsupported extension API version \"${manifestVersion}\". This runtime supports apiVersion 1.`)\n if (entry.manifest?.name && entry.manifest.name !== entry.name)\n return failed(`Extension manifest name mismatch: folder is \"${entry.name}\" but manifest declares \"${entry.manifest.name}\".`)\n if (version)\n createLogger(sink, { extension: entry.name }).warn(version.message)\n try {\n const mod = await withTimeout(\n importExtensionEntry(entry.entry, entry.dir, compileCache !== undefined ? { compileCache } : {}),\n timeoutMs,\n `Timed out importing ${entry.entry} after ${timeoutMs}ms. The module's top-level code may still be running; an import can't be cancelled.`,\n )\n const where = { name: entry.name, dir: entry.dir, source: entry.source, ...(entry.manifest ? { manifest: entry.manifest } : {}) }\n const exported = moduleExports(mod)\n const definition = definitionExport(exported)\n if (definition !== undefined)\n return runtimeLoadedExtension(definition, where)\n return failed(`Module at ${entry.entry} does not export an extension definition (expected \\`default\\` or named \\`extension\\` with \\`apiVersion: 1\\`).`)\n }\n catch (err) {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: `Failed to import ${entry.entry}: ${errorMessage(err)}`,\n }\n }\n}\n\n/**\n * Imports one extension folder fresh from disk, as discovery would (version\n * check, manifest, module graph evicted first). Never rejects: a folder with\n * no entry file or a module that fails to import comes back with `error`.\n * The caller decides whether the folder may load at all (trust, precedence):\n * {@link loadableExtensionDirs} answers that.\n *\n * @param dir - The extension folder.\n * @param opts - Its source, the import bound (default 10 s), the warning sink and Node's compile cache.\n * @param opts.source - Where the folder was found.\n * @param opts.importTimeoutMs - How long the import may take.\n * @param opts.sink - Where load warnings go. Default: warnings to stderr.\n * @param opts.compileCache - Node hosts: the folder compiled code is kept in.\n * @returns The loaded entry, or one carrying the error.\n */\nexport async function loadExtensionFolder(dir: string, opts: { source: LoadedExtensionSource, importTimeoutMs?: number, sink?: LogSink, compileCache?: string }): Promise<LoadedExtension> {\n const name = basename(dir)\n const entry = findEntryFile(dir)\n const manifest = readExtensionManifest(dir)\n if (!entry)\n return { name, dir, source: opts.source, ...manifest, error: `No entry file (${ENTRY_CANDIDATES.join(', ')}) in ${dir}.` }\n return loadExtensionFromDisk({ name, dir, entry, source: opts.source, ...manifest }, opts.importTimeoutMs ?? 10_000, opts.sink ?? consoleSink({ minLevel: 'warn' }), opts.compileCache)\n}\n\n/**\n * The export the runtime validates: one claiming `apiVersion: 1`, else the\n * first object (its validation then says what is missing).\n *\n * @param exported - A module's `default` and `extension` exports.\n * @returns The export, or `undefined` when none is an object.\n */\nexport function definitionExport(exported: readonly unknown[]): unknown {\n return exported.find(looksLikeDefinition) ?? exported.find(value => !!value && typeof value === 'object')\n}\n\n/** The `default` and `extension` exports of a module, in that order of preference. */\nfunction moduleExports(mod: unknown): unknown[] {\n if (!mod || typeof mod !== 'object')\n return []\n const m = mod as Record<string, unknown>\n return [m.default, m.extension].filter(value => value !== undefined)\n}\n\n/**\n * Tag an inline (host-passed) extension into the same shape disk-\n * loaded ones produce, so host-bundled extensions (e.g. those passed\n * via `RunTuiOptions.extensions`) flow through `buildExtensionRegistry`\n * identically to disk-discovered ones. The runtime validates the\n * definition and lists it inactive with what is wrong.\n *\n * @param definition - Extension definition the host built in code.\n */\nexport function inlineLoadedExtension(definition: Pick<ExtensionDefinition, 'apiVersion'> | object): LoadedExtension {\n if (definition && typeof definition === 'object')\n return runtimeLoadedExtension(definition, { source: 'inline' })\n return { name: 'unknown', source: 'inline', error: NO_DEFINITION_ERROR }\n}\n\n/**\n * Read `extension.json` (or `package.json#zidane`) as a shape-validated\n * manifest. Exported for the installer so \"what counts as a manifest\" has\n * exactly one definition — a malformed manifest is ignored identically at\n * install-validation time and at discovery time.\n */\nexport function readExtensionManifest(dir: string): { manifest?: ExtensionManifest } {\n const fromExtensionJson = readJson(resolve(dir, 'extension.json'))\n if (isExtensionManifest(fromExtensionJson))\n return { manifest: fromExtensionJson }\n const pkg = readJson(resolve(dir, 'package.json'))\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n const manifest = (pkg as { zidane?: unknown }).zidane\n if (isExtensionManifest(manifest))\n return { manifest }\n }\n return {}\n}\n\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch {\n return null\n }\n}\n\nfunction isExtensionManifest(value: unknown): value is ExtensionManifest {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const v = value as Record<string, unknown>\n // `apiVersion`, when present, must be a number. We still recognize an\n // unsupported version (e.g. 1) as a manifest so the loader can surface a\n // clear \"unsupported API version\" error rather than silently ignoring it —\n // but the rest of the shape is still validated.\n if (v.apiVersion !== undefined && typeof v.apiVersion !== 'number')\n return false\n return (v.name === undefined || typeof v.name === 'string')\n && (v.displayName === undefined || typeof v.displayName === 'string')\n && (v.description === undefined || typeof v.description === 'string')\n && (v.icon === undefined || isSettingsIcon(v.icon))\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/** Re-export for tests / advanced hosts that want to log the scan results before loading. */\nexport type { ExtensionScanEntry }\n/** Re-export so callers can read the canonical entry file list. */\nexport { ENTRY_CANDIDATES }\n"],"mappings":"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"}