zidane 9.1.1 → 9.1.4

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-Cl4gCZN6.js} +7 -7
  2. package/dist/{acp-Di9WvMDF.js.map → acp-Cl4gCZN6.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-DBl2JUC6.js} +2 -2
  11. package/dist/{auth-store-port-DFgf22oo.js.map → auth-store-port-DBl2JUC6.js.map} +1 -1
  12. package/dist/{auto-update-restart-BFB7Ek87.js → auto-update-restart-CF_kjcJd.js} +277 -41
  13. package/dist/auto-update-restart-CF_kjcJd.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-DIV9c5Nr.js} +4 -4
  27. package/dist/{cli-shared-CabruH8Z.js.map → cli-shared-DIV9c5Nr.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-D71duF64.js} +2 -2
  50. package/dist/{credentials-wwETW8rI.js.map → credentials-D71duF64.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-DD4wPWKS.js} +4 -4
  58. package/dist/{discovery-DycXFxdA.js.map → discovery-DD4wPWKS.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-BDmtgr2O.js} +6 -6
  62. package/dist/{engine-VQ86XGdG.js.map → engine-BDmtgr2O.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-EWOhLyd9.js} +4 -4
  68. package/dist/{executor-C7RQrkEq.js.map → executor-EWOhLyd9.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-DklXPjiq.js} +8 -8
  74. package/dist/{extensions-BCx4QuGB.js.map → extensions-DklXPjiq.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-BtMW9KzL.js} +2 -2
  82. package/dist/{google-Crva32sk.js.map → google-BtMW9KzL.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-BHMs9jsN.js → host-DEoEzQal.js} +13 -4
  89. package/dist/host-DEoEzQal.js.map +1 -0
  90. package/dist/{host-scoped-settings-CrbcqOkP.js → host-scoped-settings-WBYaCork.js} +2 -2
  91. package/dist/{host-scoped-settings-CrbcqOkP.js.map → host-scoped-settings-WBYaCork.js.map} +1 -1
  92. package/dist/{host-signals-Jsjd_-4g.js → host-signals-91zyko_i.js} +5 -5
  93. package/dist/{host-signals-Jsjd_-4g.js.map → host-signals-91zyko_i.js.map} +1 -1
  94. package/dist/{host-wO_6GW0y.js → host-uP3t08w8.js} +276 -103
  95. package/dist/host-uP3t08w8.js.map +1 -0
  96. package/dist/{host-version-CNAr9pov.js → host-version-_RHlAB29.js} +2 -2
  97. package/dist/{host-version-CNAr9pov.js.map → host-version-_RHlAB29.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-CatnsVEd.js} +4 -4
  115. package/dist/{ipc-BzWhIybr.js.map → ipc-CatnsVEd.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-a8C9eby4.js} +9 -9
  121. package/dist/{links-CJG_rFvf.js.map → links-a8C9eby4.js.map} +1 -1
  122. package/dist/{loaded-entry-DTURJo_R.js → loaded-entry-D6jzt5HQ.js} +2 -2
  123. package/dist/{loaded-entry-DTURJo_R.js.map → loaded-entry-D6jzt5HQ.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-CJy52DNi.js} +132 -31
  128. package/dist/memory-CJy52DNi.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-CAWz-sMq.js} +3 -3
  137. package/dist/{path-display-COVV71Yu.js.map → path-display-CAWz-sMq.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-zVRxiJDv.js} +5 -5
  162. package/dist/providers-zVRxiJDv.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-CBoYDIND.js} +2 -2
  168. package/dist/{registry-BKjeBnoy.js.map → registry-CBoYDIND.js.map} +1 -1
  169. package/dist/{registry-B6Ww76iW.js → registry-CQOU5jvF.js} +2 -2
  170. package/dist/{registry-B6Ww76iW.js.map → registry-CQOU5jvF.js.map} +1 -1
  171. package/dist/{registry-B9INJwJR.js → registry-YUUqbYGr.js} +324 -39
  172. package/dist/registry-YUUqbYGr.js.map +1 -0
  173. package/dist/{remote-hosts-BMWoHCWO.js → remote-hosts-CT8Q95dR.js} +4 -4
  174. package/dist/{remote-hosts-BMWoHCWO.js.map → remote-hosts-CT8Q95dR.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-_jh2OtiL.js} +6 -6
  185. package/dist/{setting-BQBSyUq0.js.map → setting-_jh2OtiL.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-BT0_oKjO.js} +3 -3
  189. package/dist/{settings-keys-iI_Y-7MC.js.map → settings-keys-BT0_oKjO.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-DxJ-lRhu.js} +3 -3
  203. package/dist/{system-freeze-m5o1sGIM.js.map → system-freeze-DxJ-lRhu.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-DwQI12WA.js} +2 -2
  231. package/dist/{version-DqqqvHDP.js.map → version-DwQI12WA.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-Dxwxb7QQ.js} +2 -2
  235. package/dist/{wire-CLm2rMK0.js.map → wire-Dxwxb7QQ.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-CA3o2nie.js} +4 -4
  239. package/dist/{ws-DfGGYQ4W.js.map → ws-CA3o2nie.js.map} +1 -1
  240. package/dist/{ws-client-CFMbhsNq.js → ws-client-C0iPAAzc.js} +2 -2
  241. package/dist/{ws-client-CFMbhsNq.js.map → ws-client-C0iPAAzc.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":"transcript-anchors-CiSi_UBn.js","names":[],"sources":["../src/chat/boot-profiler.ts","../src/chat/completion.ts","../src/chat/completion-artifacts.ts","../src/chat/config-context.tsx","../src/chat/discovery-context.tsx","../src/chat/discovery-slot.ts","../src/chat/editor-command.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/hints.ts","../src/chat/interactions.tsx","../src/chat/mcp-auth-context.tsx","../src/chat/mcp-rows.ts","../src/chat/mcp-tool-toggle.ts","../src/chat/oauth-redirect.ts","../src/chat/running-calls.ts","../src/chat/safe-mode-context.tsx","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/transcript-anchors.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Boot-time wall-clock profiler.\n//\n// Sprinkle `bootTick('<label>')` at strategic points along the startup\n// chain (cli.ts → runTui → registerTreeSitterParsers → resolveConfig →\n// createCliRenderer → React mount → discovery effects → session\n// resume). When `ZIDANE_BOOT_PROFILE=1` is set, each call emits one\n// line on stderr with the delta since the previous tick and the total\n// since module load:\n//\n// $ ZIDANE_BOOT_PROFILE=1 zidane\n// [boot] +0.0ms / total 0.0ms / cli:enter\n// [boot] +12.3ms / total 12.3ms / cli:after-setupBundledTreeSitterAssets\n// [boot] +47.8ms / total 60.1ms / runTui:after-registerTreeSitterParsers\n// ...\n//\n// Renderer-agnostic on purpose — lives in `zidane/chat` so a future GUI\n// shell can wire it into its own boot path with the same env var and\n// the same output format. Writes to stderr, which OpenTUI's renderer\n// leaves alone (stdout is the frame buffer surface).\n// ---------------------------------------------------------------------------\n\nconst enabled = !!process.env.ZIDANE_BOOT_PROFILE\n/**\n * High-resolution origin for the running profile. Captured at module\n * load time so the first {@link bootTick} call inside the same process\n * reflects \"time since the boot profiler was first reached\", which for\n * the standard `cli.ts → runTui` path is effectively \"time since the\n * TUI binary started executing user code\".\n */\nconst start = performance.now()\nlet last = start\n\n/**\n * Record a checkpoint. No-op unless `ZIDANE_BOOT_PROFILE` is truthy in\n * the environment.\n *\n * The leading delta is the time since the PREVIOUS tick (so a long\n * delta highlights the immediately-preceding work); the trailing total\n * is the time since the profiler started. Both round to one decimal of\n * a millisecond.\n *\n * @param label - Name of the boot step that just finished.\n */\nexport function bootTick(label: string): void {\n if (!enabled)\n return\n const now = performance.now()\n const delta = now - last\n const total = now - start\n last = now\n // Delta and total are padded to 7 characters so the columns stay aligned\n // until a value widens past that. The label is not padded.\n process.stderr.write(\n `[boot] +${delta.toFixed(1).padStart(7)}ms / total ${total.toFixed(1).padStart(7)}ms / ${label}\\n`,\n )\n}\n\n/**\n * Returns `true` when `ZIDANE_BOOT_PROFILE` is set. Useful for guarding\n * heavier instrumentation (e.g. wrapping a costly call in a span) that\n * you don't want paying its own cost in the default path.\n */\nexport function bootProfileEnabled(): boolean {\n return enabled\n}\n","/**\n * Prompt autocompletion framework — React hook.\n *\n * The provider contract types + pure span/reference helpers live in\n * `./completion-core` (node-free + react-free, re-exported here). This\n * module adds the `useCompletion()` React hook on top.\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A GUI consumes the same hook to drive a dropdown. The popup component\n * itself only reads `label` + `description` on each item, so provider-\n * specific typing (`TItem`) stays at the registration boundary and never\n * leaks into the renderer.\n */\n\nimport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion-core'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { applyInsert, collectReferences, findActiveTriggers, mergeSuggestions } from './completion-core'\n\nexport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n CompletionSubmitContext,\n} from './completion-core'\nexport { applyInsert, collectReferences, findActiveTrigger, findActiveTriggers, mergeReferences, mergeSuggestions, resolveCompletionSubmit } from './completion-core'\n\n/**\n * Shared sentinel returned by `useCompletion` when no references are\n * present in the buffer. Stable identity lets consumers' `useEffect`\n * dep arrays bail out on chip-less keystrokes — see `useChipHighlights`.\n */\nconst EMPTY_REFS: readonly CompletionReference<unknown>[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// React hook\n// ---------------------------------------------------------------------------\n\n/** State surface returned by `useCompletion()`. */\nexport interface CompletionState<TItem = unknown> {\n /** Active trigger, or null when the cursor doesn't sit in a completable span. */\n active: ActiveTrigger<TItem> | null\n /** Items for the active query. Empty when no trigger is active. */\n items: readonly CompletionItem<TItem>[]\n /** True while the provider's `suggest` promise is pending. */\n loading: boolean\n /** Index of the highlighted item inside `items`. */\n selectedIndex: number\n /** Move the highlight. Wraps at the ends. No-op when `items` is empty. */\n selectNext: () => void\n /** Moves the highlight to the previous item. Wraps at the ends. No-op when `items` is empty. */\n selectPrev: () => void\n /**\n * Commit the highlighted item. Returns the new text + cursor pair, or\n * `null` when there's nothing to commit (no active trigger, empty list).\n */\n commit: () => { text: string, cursor: number } | null\n /** Force-close the popover until the user types again. */\n dismiss: () => void\n /** All references in the current buffer, regardless of provider. */\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Drive a prompt-completion popover from React state.\n *\n * Inputs are pull-style — the caller owns `text` + `cursor` (typically\n * mirroring an `OpenTUI TextareaRenderable` or a `<textarea>` element) and\n * passes them in each render. Providers are matched by their `trigger`\n * character; every provider that shares the trigger at the cursor is bound\n * and their suggestions merge into one list.\n *\n * Asynchronous suggest calls are aborted when the query changes or the\n * popover closes, so providers that hit a backend don't leak.\n *\n * @param input - Current buffer text and cursor offset.\n * @param providers - Providers that offer suggestions.\n * @param options - Options. `maxQueryLength` bounds the trigger query scan.\n */\nexport function useCompletion<TItem = unknown>(\n input: { text: string, cursor: number },\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): CompletionState<TItem> {\n const { text, cursor } = input\n // Destructure primitives up front so the `useMemo` dep array reads them\n // by value (not by `options` reference, which is a fresh object literal\n // on every call from most hosts).\n const { maxQueryLength } = options\n // `dismissed` is keyed by the trigger's `span.start` — the buffer\n // position of the `@` / `/` glyph that opened the popover. Staying\n // dismissed *while the user keeps typing into the same trigger span*\n // is the whole point of Esc; a stricter `${text.length}:${cursor}`\n // key would re-open on the very next keystroke, which would make the\n // \"esc dismiss\" hint a lie. The effect below resets this when the trigger\n // goes away entirely (user backspaced past `@`) so retyping `@` at\n // the same position works.\n const [dismissedTriggerStart, setDismissedTriggerStart] = useState<number | null>(null)\n\n const rawTrigger = useMemo(\n () => findActiveTriggers(text, cursor, providers, { maxQueryLength }),\n [text, cursor, providers, maxQueryLength],\n )\n\n const activeMulti = useMemo(() => {\n if (!rawTrigger)\n return null\n if (rawTrigger.span.start === dismissedTriggerStart)\n return null\n return rawTrigger\n }, [rawTrigger, dismissedTriggerStart])\n\n // `active` stays a single-provider ActiveTrigger (first bound provider) so\n // the popup's title/commit contract is unchanged; multi-provider merging\n // happens in the suggest effect below and surfaces as `item.section`.\n const active = useMemo<ActiveTrigger<TItem> | null>(\n () => activeMulti\n ? { provider: activeMulti.providers[0], query: activeMulti.query, span: activeMulti.span }\n : null,\n [activeMulti],\n )\n\n // Forget the dismissal once the trigger disappears — otherwise a\n // fresh `@` typed at the same byte position would inherit the prior\n // dismiss and the popover would never come back without scrolling\n // the trigger elsewhere first.\n useEffect(() => {\n if (!rawTrigger)\n setDismissedTriggerStart(null)\n }, [rawTrigger])\n\n const [items, setItems] = useState<readonly CompletionItem<TItem>[]>([])\n const [loading, setLoading] = useState(false)\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Cancel any in-flight suggest when the query changes / popover closes.\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => {\n abortRef.current?.abort()\n if (!activeMulti) {\n setItems([])\n setLoading(false)\n setSelectedIndex(0)\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n const ctx: CompletionContext = { text, cursor }\n let cancelled = false\n const outs = activeMulti.providers.map(provider => ({\n provider,\n out: provider.suggest(activeMulti.query, ctx, controller.signal),\n }))\n // Sync fast path — every provider returned synchronously (the common\n // single-provider case, e.g. `@` files). Merge without a loading flash.\n if (outs.every(o => Array.isArray(o.out))) {\n setItems(mergeSuggestions(outs.map(o => ({ provider: o.provider, items: o.out as CompletionItem<TItem>[] })), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n // allSettled, not all: with several providers on one trigger, a single\n // provider whose async suggest rejects (e.g. a failed catalog scan) must\n // not blank its siblings' results. Fulfilled groups merge; rejected ones\n // are dropped. (All rejecting → empty items.)\n void Promise.allSettled(outs.map(async o => ({ provider: o.provider, items: await o.out }))).then(\n (results) => {\n if (cancelled)\n return\n setItems(mergeSuggestions(results.flatMap(r => r.status === 'fulfilled' ? [r.value] : []), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [activeMulti, text, cursor])\n\n // References don't depend on `cursor` — both built-in providers ignore\n // `ctx.cursor` in `parseReferences`, and the consumers (chip highlighting,\n // submit-time activation) only care about positions of references, not\n // about where the caret happens to sit. Dropping `cursor` from the dep\n // array avoids re-walking every provider regex on each arrow-key press.\n // `cursor` is still forwarded so a future provider that wants the cursor\n // can opt in — at the cost of choosing its own memoization strategy.\n //\n // Empty results collapse to a shared `EMPTY_REFS` constant so the prompt\n // textarea's `useChipHighlights` effect doesn't fire on every keystroke\n // of a chip-less buffer (each keystroke would otherwise allocate a fresh\n // `[]`, change identity, and trigger a `clearAllHighlights` repaint).\n const references = useMemo(\n () => {\n const refs = collectReferences(text, providers, cursor)\n return refs.length === 0 ? (EMPTY_REFS as readonly CompletionReference<TItem>[]) : refs\n },\n [text, providers],\n )\n\n const selectNext = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i + 1) % items.length))\n }, [items.length])\n const selectPrev = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i - 1 + items.length) % items.length))\n }, [items.length])\n const dismiss = useCallback(() => {\n // Pull from `rawTrigger`, not `active`, so dismiss-while-dismissed\n // (a no-op now but reasonable to support) keeps the right anchor.\n if (rawTrigger)\n setDismissedTriggerStart(rawTrigger.span.start)\n }, [rawTrigger])\n const commit = useCallback((): { text: string, cursor: number } | null => {\n if (!active || items.length === 0)\n return null\n const item = items[Math.min(selectedIndex, items.length - 1)]\n return applyInsert(text, active.span, item.insertText)\n }, [active, items, selectedIndex, text])\n\n // Memoize the returned bag so consumers placing it in a `useEffect`\n // dep array don't observe a fresh reference every render. Same pattern\n // (and rationale) as `useStreamBuffer.return` — see streaming.ts.\n return useMemo<CompletionState<TItem>>(\n () => ({ active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references }),\n [active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references],\n )\n}\n","/**\n * Completion provider for session artifacts. Trigger: `#` (shared with the\n * chats provider — the renderer groups both under one popup).\n *\n * `#pr` → \"acme/widgets#42\" (pull request, open) → inserts the PR URL. Unlike chats there\n * is no token grammar: the inserted text IS the locator (URL, `git:…`,\n * path), which the model can act on directly and a human can read. No\n * `parseReferences` — a pasted URL is not a reference to resolve.\n *\n * Catalog getter is host-owned and sync; the host keeps it fresh off the\n * `artifact:recorded` / `artifact:changed` signals the same way it keeps\n * the chats catalog. `ensureCatalog` covers the cold first `#` keystroke.\n */\n\nimport type { ExtensionArtifactRow } from '../extensions/types'\nimport type { SessionArtifact } from '../session/artifacts'\nimport type { CompletionItem, CompletionProvider } from './completion-core'\nimport { Fzf } from 'fzf'\nimport { ageString } from './format'\n\n/** Id of the artifacts completion provider. */\nexport const ARTIFACTS_COMPLETION_PROVIDER_ID = 'artifacts'\n\n/** An artifact that can be referenced from a prompt. */\nexport interface ArtifactReferenceEntry {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Pre-rendered row (`renderArtifactRow`) — kind registry stays host-side. */\n row: ExtensionArtifactRow\n /** Human kind label for the description (`Pull request`). */\n kindLabel: string\n}\n\n/** Provider payload for an artifact reference. */\nexport interface ArtifactReferenceData {\n /** The referenced artifact. */\n entry: ArtifactReferenceEntry\n /** What gets inserted: `row.href` when present, else the raw `uri`. */\n locator: string\n}\n\nconst DEFAULT_RESULT_LIMIT = 30\n\n/** Kind → short query aliases so `#pr` and `#branch` hit without typing the label. */\nconst KIND_ALIASES: Record<string, readonly string[]> = {\n pull_request: ['pr', 'pull', 'mr'],\n branch: ['branch', 'br'],\n commit: ['commit', 'sha'],\n file: ['file', 'path'],\n}\n\nfunction locatorFor(entry: ArtifactReferenceEntry): string {\n return entry.row.href ?? entry.artifact.uri\n}\n\nfunction haystack(entry: ArtifactReferenceEntry): string {\n const aliases = KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]\n return [entry.row.title, entry.row.subtitle ?? '', entry.artifact.label, entry.kindLabel, ...aliases].join(' ')\n}\n\nfunction descriptionFor(entry: ArtifactReferenceEntry): string {\n const bits = [entry.kindLabel]\n if (entry.row.status)\n bits.push(entry.row.status)\n bits.push(ageString(entry.artifact.updatedAt))\n const loc = locatorFor(entry)\n if (loc !== entry.row.title)\n bits.push(loc)\n return bits.join(', ')\n}\n\n/**\n * Filters and ranks artifacts for a query.\n * An empty query returns every artifact. Otherwise it matches the kind aliases, kind label and a fuzzy text match.\n *\n * @param catalog - Artifacts to search.\n * @param query - Text typed after the trigger.\n * @param limit - Maximum number of items returned.\n */\nexport function scoreArtifacts(\n catalog: readonly ArtifactReferenceEntry[],\n query: string,\n limit = DEFAULT_RESULT_LIMIT,\n): CompletionItem<ArtifactReferenceData>[] {\n const q = query.trim().toLowerCase()\n const fuzzy = q.length > 0\n ? new Set(new Fzf(catalog, { selector: haystack, casing: 'case-insensitive' }).find(q).map(r => r.item))\n : null\n const scored: { entry: ArtifactReferenceEntry, rank: number }[] = []\n for (const entry of catalog) {\n if (q.length === 0) {\n scored.push({ entry, rank: 5 })\n continue\n }\n const title = entry.row.title.toLowerCase()\n const aliases = (KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]).map(a => a.toLowerCase())\n const kindLabel = entry.kindLabel.toLowerCase()\n if (aliases.some(a => a === q || a.startsWith(q)) || kindLabel.startsWith(q)) {\n scored.push({ entry, rank: 0 })\n continue\n }\n if (title.startsWith(q)) {\n scored.push({ entry, rank: 1 })\n continue\n }\n if (title.includes(q) || locatorFor(entry).toLowerCase().includes(q)) {\n scored.push({ entry, rank: 2 })\n continue\n }\n if (fuzzy?.has(entry))\n scored.push({ entry, rank: 4 })\n }\n scored.sort((a, b) => a.rank - b.rank || b.entry.artifact.updatedAt - a.entry.artifact.updatedAt)\n return scored.slice(0, limit).map<CompletionItem<ArtifactReferenceData>>(({ entry }) => {\n const locator = locatorFor(entry)\n return {\n id: `artifact:${entry.artifact.id}`,\n label: entry.row.title,\n description: descriptionFor(entry),\n insertText: `${locator} `,\n data: { entry, locator },\n }\n })\n}\n\n/**\n * Creates a completion provider for `#` artifact references.\n * The provider only suggests. It inserts the artifact's locator as plain text, so it parses no references back.\n *\n * @param opts - Catalog getter, an optional async catalog loader and a result limit (default 30).\n */\nexport function createArtifactsCompletionProvider(opts: {\n getCatalog: () => readonly ArtifactReferenceEntry[]\n ensureCatalog?: () => Promise<readonly ArtifactReferenceEntry[]>\n limit?: number\n}): CompletionProvider<ArtifactReferenceData> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n return {\n id: ARTIFACTS_COMPLETION_PROVIDER_ID,\n trigger: '#',\n label: 'Artifacts',\n suggest(query) {\n const current = opts.getCatalog()\n if (current.length > 0 || !opts.ensureCatalog)\n return scoreArtifacts(current, query, limit)\n return opts.ensureCatalog().then(loaded => scoreArtifacts(loaded, query, limit))\n },\n // Inserted text is a plain locator, not a token: nothing to parse back.\n parseReferences: () => [],\n }\n}\n","import type { ReactNode } from 'react'\nimport type { ResolvedConfig } from './config'\nimport { createContext, useContext } from 'react'\n\n// ---------------------------------------------------------------------------\n// ConfigContext — renderer-agnostic React context that hands a fully-resolved\n// chat config down to deep consumers (modals, pickers, screens). Kept in its\n// own file so callers that only want `resolveConfig()` don't pay the\n// `import 'react'` cost.\n// ---------------------------------------------------------------------------\n\n/** Exported for hooks that read the config when there is one (`useModelName`). */\nexport const ConfigContext = createContext<ResolvedConfig | null>(null)\n\n/**\n * Provides a {@link ResolvedConfig} to the components below it.\n *\n * @param props - Props with the resolved `config` and the `children` to render.\n */\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\n/** Reads the {@link ResolvedConfig} from the nearest {@link ConfigProvider}. Throws when used outside one. */\nexport function useConfig(): ResolvedConfig {\n const ctx = useContext(ConfigContext)\n if (!ctx)\n throw new Error('useConfig must be used inside <ConfigProvider>')\n return ctx\n}\n","/**\n * Live catalogs for files / skills / MCPs surfaced through React\n * context.\n *\n * **Why context, not props.** The modal layer stores its open node in\n * `useState`. React's element-identity reconciliation skips re-rendering\n * a subtree when the element reference hasn't changed — so passing\n * `skillsCatalog` as a prop into `modal.open(<SettingsModal …>)` bakes\n * the snapshot at open time and any subsequent `setSkillsCatalog`\n * doesn't reach the modal. Context propagation IS forwarded through\n * the modal layer regardless of element identity (same path the MCP\n * auth state uses), so reading via `useDiscovery()` gives the modal\n * live state.\n *\n * **Shape.** The provider exposes three groups:\n *\n * - **Catalogs** — the current state. Consumers re-render on every\n * update.\n * - **Ensure thunks** — kick the first load (idempotent). Wired into\n * completion providers; also called by the settings modal on mount\n * to fire-load lazy catalogs (currently `files`) on demand.\n * - **Refresh thunks** — force a rescan, bypassing the SWR throttle.\n * Wired into the settings modal's `ctrl+R`.\n *\n * Renderer-agnostic by design: the context only carries data + thunks.\n * The discovery work itself lives in `*-discovery.ts` modules; the\n * stale-while-revalidate machinery lives in `discovery-slot.ts`. The\n * provider's caller (AppShell for the TUI) owns where the slots live\n * and how their state propagates to `<DiscoveryProvider value>`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpToolSchema } from '../mcp'\nimport type { SkillConfig } from '../skills'\nimport type { FileEntry } from './files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from './mcps-discovery'\nimport type { DiscoveredPlugin } from './plugins-discovery'\nimport { createContext, useContext } from 'react'\n\n/** Skills, plugins and MCP servers found on disk, shared with the UI. */\nexport interface DiscoveryContextValue {\n /** Parsed `SkillConfig[]` from project + user scan paths. */\n skillsCatalog: readonly SkillConfig[]\n /**\n * Agent Plugins loaded from `.agents/plugins` / `.{prefix}/plugins`\n * (project + user). Includes rejected plugins — they render as read-only\n * diagnostics rather than vanishing.\n */\n pluginsCatalog: readonly DiscoveredPlugin[]\n /** Parsed `mcps.json` entries. */\n mcpsCatalog: readonly DiscoveredMcp[]\n /** Non-fatal parse errors from `mcps.json` lookups. */\n mcpsErrors: readonly DiscoveryError[]\n /**\n * Per-server full tool catalog from the on-disk cache (see\n * `chat/mcp-tools-cache.ts`). Updated as MCP servers bootstrap and\n * fire the `mcp:tools:list` hook. Missing key → no cache yet; the\n * settings UI surfaces an empty-state row under the expanded server.\n */\n mcpToolsByServer: Record<string, readonly McpToolSchema[]>\n /** Current file walk results — empty until lazy-load lands. */\n filesCatalog: readonly FileEntry[]\n\n /** Force a fresh skills rescan. Resolves once state is committed. */\n refreshSkills: () => Promise<void>\n /** Force a fresh plugin rescan. Resolves once state is committed. */\n refreshPlugins: () => Promise<void>\n /**\n * Re-parse `mcps.json`. Sync-shaped under the hood, async for symmetry.\n * Resolves `true` when the catalog actually changed (a publish that\n * schedules downstream agent rebuilds), `false` on a fingerprint no-op\n * or scan failure — callers that arm rebuild-coupled state (e.g. the\n * add-MCP auto-login) disarm on `false`.\n */\n refreshMcps: () => Promise<boolean>\n /** Force a fresh file walk. No UI affordance calls it yet. */\n refreshFiles: () => Promise<void>\n\n /** Idempotent first-load trigger for skills (used by `/` popover + modal mount). */\n ensureSkills: () => Promise<readonly SkillConfig[]>\n /** Idempotent first-load trigger for files (used by `@` popover). */\n ensureFiles: () => Promise<readonly FileEntry[]>\n}\n\nconst DiscoveryContext = createContext<DiscoveryContextValue | null>(null)\n\n/**\n * Provides a {@link DiscoveryContextValue} to the components below it.\n *\n * @param props - Props with the discovery `value` and the `children` to render.\n */\nexport function DiscoveryProvider({\n value,\n children,\n}: {\n value: DiscoveryContextValue\n children: ReactNode\n}) {\n return (\n <DiscoveryContext.Provider value={value}>\n {children}\n </DiscoveryContext.Provider>\n )\n}\n\n/**\n * Read live discovery state + actions. Throws if used outside a\n * `<DiscoveryProvider>` — discovery is a load-bearing dependency for\n * settings + completion popovers; bailing loud here surfaces wiring\n * mistakes at mount instead of producing empty catalogs at first\n * keystroke.\n */\nexport function useDiscovery(): DiscoveryContextValue {\n const ctx = useContext(DiscoveryContext)\n if (!ctx)\n throw new Error('useDiscovery must be used inside <DiscoveryProvider>')\n return ctx\n}\n\n/**\n * Non-throwing variant — returns `null` when no provider is mounted.\n * Used by composable modals (`<SettingsModal>`, …) that accept catalog\n * props as a fallback for embedders who don't wire the full TUI\n * shell. The in-app flow always mounts `<DiscoveryProvider>` so the\n * modal sees live state; standalone embeds get the prop snapshot.\n */\nexport function useDiscoveryOptional(): DiscoveryContextValue | null {\n return useContext(DiscoveryContext)\n}\n","/**\n * Stale-while-revalidate catalog slot.\n *\n * One slot per (catalog kind, projectDir) pin. Holds the first-load\n * promise + a `lastScannedAt` timestamp + an in-flight `refreshing`\n * promise. Idempotent across concurrent callers, safe to rotate when\n * the project changes (the slot's `abort` controller cancels in-flight\n * walks and the `.then` guards drop stale results).\n *\n * Renderer-agnostic. AppShell wraps it in React refs so React closures\n * stay stable; a non-TUI shell can use it directly against its own\n * state propagation.\n *\n * Contract:\n * - `ensure()` — kick the first load if not started; return the\n * cached promise. On subsequent calls, if `Date.now() -\n * lastScannedAt >= throttleMs` and no refresh is in flight, kick\n * a background revalidate that calls `onLoad(fresh)` when it\n * resolves. The original first-load promise still resolves with\n * the first walk's contents — consumers reading live state\n * elsewhere (React state) see the refreshed catalog.\n * - `refresh()` — force a rescan now, bypassing the throttle.\n * De-duplicates: if a refresh is already in flight, returns the\n * same promise.\n * - `isRefreshing()` — whether a refresh is running now.\n * - `abort()` — cancel any in-flight walk and detach. After this\n * the slot is dead; callers should rotate to a new slot.\n *\n * No state is exposed except through promises + the host's `onLoad`\n * callback; the slot doesn't try to be a store, only a coordinator.\n */\n\n/** Walk function — takes an abort signal, returns the discovered list. */\nexport type Walk<T> = (signal: AbortSignal) => Promise<readonly T[]>\n\n/** Notification: a fresh walk has resolved. Host is expected to commit to its own state store. */\nexport type Commit<T> = (items: readonly T[]) => void\n\n/** Options for {@link createDiscoverySlot}. */\nexport interface DiscoverySlotOptions<T> {\n /** Discover function — runs the actual FS / network walk. */\n walk: Walk<T>\n /** Commit hook — receives every successful walk's result (first + refreshes). */\n onLoad: Commit<T>\n /**\n * Min interval between background refreshes triggered by `ensure()`.\n * Default: 3_000ms. `refresh()` ignores this — it's the user's\n * explicit-intent path.\n */\n throttleMs?: number\n /** Optional error sink. Defaults to no-op; useful for `debugLog`. */\n onError?: (err: unknown, phase: 'first-load' | 'refresh') => void\n}\n\n/** Handle returned by {@link createDiscoverySlot}. */\nexport interface DiscoverySlot<T> {\n /**\n * Kick the first load (idempotent). Returns the first-load promise\n * once it exists. Schedules a background refresh if past throttle.\n */\n ensure: () => Promise<readonly T[]>\n /** Force-refresh now. De-duped across concurrent callers. */\n refresh: () => Promise<void>\n /** True if a refresh is currently running. */\n isRefreshing: () => boolean\n /** Cancel in-flight work + detach. Idempotent. */\n abort: () => void\n}\n\n/**\n * Creates a slot that loads a list lazily and keeps it fresh.\n * `ensure()` starts the first walk once and schedules throttled background refreshes. `refresh()` forces a walk. Every successful walk result goes to `onLoad`.\n *\n * @param options - The walk function, commit hook and optional throttle and error sink.\n */\nexport function createDiscoverySlot<T>(options: DiscoverySlotOptions<T>): DiscoverySlot<T> {\n const throttleMs = options.throttleMs ?? 3_000\n const abortCtrl = new AbortController()\n let firstLoad: Promise<readonly T[]> | null = null\n let refreshing: Promise<void> | null = null\n let lastScannedAt = 0\n let aborted = false\n\n const handleError = (err: unknown, phase: 'first-load' | 'refresh'): void => {\n options.onError?.(err, phase)\n }\n\n const startRefresh = (): Promise<void> => {\n lastScannedAt = Date.now()\n const run = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return\n options.onLoad(items)\n })\n .catch((err) => {\n if (aborted)\n return\n handleError(err, 'refresh')\n })\n .finally(() => {\n if (refreshing === run)\n refreshing = null\n })\n refreshing = run\n return run\n }\n\n const slot: DiscoverySlot<T> = {\n ensure() {\n if (aborted)\n return Promise.resolve([] as readonly T[])\n if (!firstLoad) {\n lastScannedAt = Date.now()\n firstLoad = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return [] as readonly T[]\n options.onLoad(items)\n return items\n })\n .catch((err) => {\n if (!aborted)\n handleError(err, 'first-load')\n return [] as readonly T[]\n })\n return firstLoad\n }\n // SWR: revalidate in the background if stale and not already running.\n if (!refreshing && Date.now() - lastScannedAt >= throttleMs)\n void startRefresh()\n return firstLoad\n },\n refresh() {\n if (aborted)\n return Promise.resolve()\n if (refreshing)\n return refreshing\n // Forced refresh from a user-initiated path (e.g. settings\n // modal `ctrl+R`). If the first load hasn't happened yet,\n // bootstrap through `ensure` so subsequent ensure callers see\n // the right `firstLoad` promise.\n if (!firstLoad)\n return slot.ensure().then(() => {})\n return startRefresh()\n },\n isRefreshing() {\n return refreshing !== null\n },\n abort() {\n if (aborted)\n return\n aborted = true\n abortCtrl.abort()\n },\n }\n return slot\n}\n","/**\n * `$EDITOR` / `$VISUAL` command parsing.\n *\n * These variables conventionally hold a command LINE, not a bare executable —\n * `code --wait`, `cursor --wait`, `subl -w`, `emacsclient -nw` are all normal\n * values. Spawning the whole string as argv[0] looks for a file literally named\n * `cursor --wait` and fails with ENOENT, so the flags have to be split off.\n *\n * Kept out of the TUI (and free of `node:child_process`) so the\n * tokenizer is unit-testable without pulling in the TUI.\n */\n\n/**\n * Split an editor command line into `[executable, ...args]`.\n *\n * Whitespace-separated, with single and double quotes honored so an editor\n * living under a path with spaces still works\n * (`\"/Applications/My App/bin/ed\" -w`). Quotes group and are stripped;\n * backslash escaping is deliberately NOT implemented, because on Windows\n * backslash is the path separator and treating `C:\\tools\\ed` as escapes would\n * corrupt far more command lines than it would rescue.\n *\n * Returns `[]` for an empty or whitespace-only spec, so callers can fall back\n * to the platform opener rather than spawning `''`.\n *\n * @param spec - Raw editor command line, such as the value of `$EDITOR`.\n * @example\n * ```ts\n * import { parseEditorCommand } from 'zidane/chat'\n *\n * const [cmd, ...args] = parseEditorCommand('code --wait')\n * // cmd === 'code', args === ['--wait']\n * ```\n */\nexport function parseEditorCommand(spec: string): string[] {\n const tokens: string[] = []\n let current = ''\n // The quote character we're inside, or null at top level. Tracked (rather\n // than a boolean) so a double quote inside single quotes stays literal.\n let quote: '\"' | '\\'' | null = null\n // Distinct from `current !== ''` so `\"\"` yields an empty-string token instead\n // of vanishing — an explicitly quoted empty argument was still an argument.\n let quoted = false\n\n for (const ch of spec) {\n if (quote) {\n if (ch === quote)\n quote = null\n else\n current += ch\n continue\n }\n if (ch === '\"' || ch === '\\'') {\n quote = ch\n quoted = true\n continue\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n if (current !== '' || quoted) {\n tokens.push(current)\n current = ''\n quoted = false\n }\n continue\n }\n current += ch\n }\n if (current !== '' || quoted)\n tokens.push(current)\n return tokens\n}\n\n/**\n * Re-quote a single argv token for a shell that will re-parse the joined command\n * line — i.e. `spawn(..., { shell: true })`, which Node uses on Windows so that\n * `.cmd` / `.bat` wrappers (how `code`, `cursor` and friends ship there) resolve.\n *\n * Node does NOT re-quote when `shell` is set: it joins `[cmd, ...args]` with\n * spaces and hands the string over. Since {@link parseEditorCommand} has already\n * STRIPPED the user's quotes, handing tokens back unquoted would split\n * `C:\\Program Files\\ed.exe` at the space and try to run `C:\\Program`. This puts\n * the quotes back.\n *\n * Only tokens that need it are quoted, so a plain `code` stays `code`. A literal\n * double quote is doubled, which is how `cmd.exe` escapes one inside a quoted\n * run.\n *\n * The test covers `cmd.exe` METACHARACTERS, not just whitespace: `&`, `|`, `^`,\n * `<`, `>` and friends carry none of the obvious \"needs quoting\" signals, so a\n * repo cloned into `C:\\work\\a&b` would otherwise hand `C:\\work\\a&b\\AGENTS.md`\n * over bare, and cmd would split it at the `&` and run the tail as a second\n * command.\n *\n * `%` and `!` are in the class so such a token is quoted rather than passed\n * bare — but be clear about the limit: quoting does NOT stop cmd.exe expanding\n * `%VAR%`, nor `!VAR!` where delayed expansion is on. Double quotes suppress\n * splitting and other syntax, not expansion. That is a property of the shell\n * which no amount of quoting can close; the worst case is a wrong path, not an\n * injected command.\n *\n * @param token - One argv token, already stripped of its original quotes.\n */\nexport function quoteForShell(token: string): string {\n if (token === '')\n return '\"\"'\n if (!/[\\s\"&|^<>%()!]/.test(token))\n return token\n return `\"${token.replace(/\"/g, '\"\"')}\"`\n}\n","import type { ReactNode } from 'react'\nimport type { BooleanSettingKey } from './settings-catalog'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { enforceSettingsInvariants } from './settings-catalog'\n\n// The React-free catalog lives in `settings-catalog.ts`.\nexport * from './settings-catalog'\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the settings.\n//\n// `onChange` is called with the latest settings whenever a row toggles, so\n// the host can persist them. Persistence pathing lives outside this module\n// to keep the settings layer portable across renderers (TUI, GUI, …).\n// ---------------------------------------------------------------------------\n\nexport interface SettingsContextValue {\n settings: Settings\n /** Flip a boolean setting in place. Restricted at the type level to boolean-valued keys. */\n toggle: (key: BooleanSettingKey) => void\n /** Write any setting key to a new value. Used by string-valued choice rows. */\n setSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void\n /** One line the Settings modal shows (a server refused the last write); cleared by the next change. */\n status?: string | null\n /**\n * Settings sync markers, while the modal edits a server another computer\n * syncs to (`docs/SERVER.md` § Settings sync): which setting came from\n * which device, which was edited on the server since (an override), and\n * dropping an override. Absent elsewhere.\n */\n sync?: SettingsSyncMarks\n}\n\n/** A setting's sync state on the server the modal edits. */\nexport interface SettingsSyncMarks {\n marker: (key: string) => { kind: 'synced' | 'override', device: string } | null\n /** Drop the override on `key`; false when it has none. */\n reset: (key: string) => boolean\n}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\n/**\n * Holds the settings state, applies {@link enforceSettingsInvariants} on every write and reports each change to `onChange`.\n *\n * @param props - `initial` settings, an optional `onChange` callback and the `children` to render.\n */\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n // Normalized on load too: an older build (or a hand edit) may have persisted\n // a dependent toggle without its requirement.\n const [settings, setSettings] = useState<Settings>(() => enforceSettingsInvariants(initial))\n\n // `onChange` runs as a side-effect *after* commit, not inside the state\n // updater. Two reasons:\n // 1. React 18 strict-mode dev runs invoke state updaters twice for\n // every transition; a `disk.save(...)` inside the updater would\n // double-fire there.\n // 2. Pure-update is the React contract — updaters must be free of\n // observable side-effects. Persisting from inside breaks the\n // contract regardless of strict mode.\n //\n // A ref keeps the latest `onChange` reachable from the effect without\n // forcing the effect to re-run on every render the parent re-renders.\n // The `initialMount` ref skips the very first commit (we don't want to\n // persist the initial state we just loaded from disk).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const initialMount = useRef(true)\n useEffect(() => {\n if (initialMount.current) {\n initialMount.current = false\n return\n }\n onChangeRef.current?.(settings)\n }, [settings])\n\n const toggle = useCallback((key: BooleanSettingKey) => {\n setSettings(prev => enforceSettingsInvariants({ ...prev, [key]: !prev[key] }))\n }, [])\n\n const setSetting = useCallback(<K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings(prev => (prev[key] === value ? prev : enforceSettingsInvariants({ ...prev, [key]: value })))\n }, [])\n\n const value = useMemo<SettingsContextValue>(\n () => ({ settings, toggle, setSetting }),\n [settings, toggle, setSetting],\n )\n\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/**\n * Re-provides settings to a subtree with other values and writers: a\n * surface that edits somewhere else than this window (the TUI's Settings\n * modal while it edits a remote host) reuses every settings-reading\n * component unchanged.\n */\nexport function SettingsOverride({ value, children }: { value: SettingsContextValue, children: ReactNode }) {\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/** Returns the settings context value. Throws when used outside {@link SettingsProvider}. */\nexport function useSettings(): SettingsContextValue {\n const ctx = useContext(SettingsContext)\n if (!ctx)\n throw new Error('useSettings must be used inside <SettingsProvider>')\n return ctx\n}\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills`, `settings.enabledMcps`,\n * `settings.enabledExtensions` and `settings.enabledPlugins` (see\n * {@link EnabledAllowlistKey}).\n *\n * Semantics, kept identical across every call site:\n * - `undefined` → every catalog entry is enabled (default — user has\n * never opened the picker; new entries flow in).\n * - `[]` → the whole subsystem is off.\n * - `[names]` → explicit allowlist; the persisted shape stays a\n * concrete array once the user has toggled anything.\n *\n * The hook exposes a live `enabledSet` (Set<string>) + a `toggle(name)`\n * callback that persists the result through `useSettings().setSetting`.\n * The first toggle seeds the persisted allowlist from the current catalog\n * so newly-added entries don't silently drop on the next launch.\n *\n * Renderer-neutral: works for the TUI's `<ToggleListModal>`, a GUI\n * checkbox list, a CLI flag tool. Hook tests live alongside the chat\n * suite; the rendering layer is tested where it lives.\n */\n\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Settings keys whose value type is `readonly string[] | undefined`. */\nexport type EnabledAllowlistKey = 'enabledSkills' | 'enabledMcps' | 'enabledExtensions' | 'enabledPlugins'\n\n/**\n * Reactive handle over a persisted enabled-names allowlist, returned by {@link useEnabledToggleSet}.\n */\nexport interface EnabledToggleSet {\n /** Live set of enabled names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Toggle one name on/off; persists immediately via `setSetting`. */\n toggle: (name: string) => void\n}\n\n/**\n * Bind an \"enabled allowlist\" setting to a discovered catalog.\n *\n * `keyOf` extracts the persisted identity from a catalog entry (skill name,\n * MCP server name, …). Pass a stable, collision-free key — the persisted\n * allowlist is keyed against it forever.\n *\n * @param opts - Catalog, key extractor, setting key, and optional extra keys to keep when pruning the allowlist.\n */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /**\n * Names that survive the persisted-allowlist GC even when absent from\n * `catalog`. Use when the catalog deliberately hides entries that are\n * still known to exist — e.g. errored / untrusted extensions are kept\n * out of the toggle list but must not be dropped from the allowlist\n * by an unrelated toggle. Defaults to the catalog keys.\n */\n gcKeys?: readonly string[]\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey, gcKeys } = opts\n const persisted = settings[settingKey]\n\n const enabledSet = useMemo<ReadonlySet<string>>(() => {\n if (persisted === undefined)\n return new Set(catalog.map(keyOf))\n return new Set(persisted)\n }, [persisted, catalog, keyOf])\n\n // Live catalog membership — used to garbage-collect stale persisted\n // names on every write. Without this, a skill / MCP that was once\n // enabled and then removed from disk lingers in the JSON forever\n // (the next toggle round-trips it back into the array). Functionally\n // harmless today (the agent ignores unknown names) but it lets the\n // persisted state drift away from reality. `gcKeys` widens the\n // \"known\" set beyond the rendered catalog (see its JSDoc).\n const catalogKeys = useMemo(\n () => new Set(gcKeys ?? catalog.map(keyOf)),\n [gcKeys, catalog, keyOf],\n )\n\n const toggle = useCallback((name: string) => {\n // Seed from the current `enabledSet` (covers both the seeded-from-catalog\n // and explicit-allowlist branches). Persist as a sorted array filtered\n // against the live catalog so stale names — entries deleted from disk\n // between launches — don't linger forever in the persisted allowlist.\n // `undefined` is reserved for \"fresh install, accept new discoveries\";\n // once the user toggles once the persistence shape stays a concrete list.\n const next = new Set(enabledSet)\n if (next.has(name))\n next.delete(name)\n else\n next.add(name)\n const persisted = [...next]\n .filter(n => catalogKeys.has(n))\n .sort()\n setSetting(settingKey, persisted)\n }, [enabledSet, catalogKeys, settingKey, setSetting])\n\n return { enabledSet, toggle }\n}\n","// ---------------------------------------------------------------------------\n// Footer / title hint primitives.\n//\n// Pure data + width-estimation helpers consumed by the TUI's bottom-bar\n// footer, the prompt-box overlay, the sessions header, and any future\n// GUI shell that wants to render the same hint rows: `<key> <label>`, two\n// blank columns between hints. Plain-text math only — no renderer primitives, no colour\n// resolution, no React. Width estimates are intentionally `.length`-\n// based (assumes ASCII / single-cell glyphs); the consumer's renderer\n// is responsible for the actual painting and any wide-glyph accounting\n// it needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Single hint entry — `<key> <label>`, optionally followed by an inline\n * secondary chord (`<key><extra.key> <label> <extra.label>`) for compound\n * shortcuts like `ctrl+m/l model effort`.\n *\n * Colour fields are advisory hex strings (resolved against the active\n * theme by the renderer); leaving them undefined lets the renderer pick\n * its conventional fallback (e.g. `warn` for keys, `dim` for labels; the\n * secondary chord takes its key's color).\n */\nexport interface Hint {\n /** Key or chord shown for the hint, such as `ctrl+l`. */\n key: string\n /** Short action text shown after the key. */\n label: string\n /** Optional override for the key color. Renderer default: warn. */\n keyColor?: string\n /** Optional override for the label color. Renderer default: dim. */\n labelColor?: string\n /**\n * Optional secondary chord + value rendered inline after the primary\n * pair: `<key><extra.key> <label> <extra.label>`. Lets one hint advertise\n * a compound shortcut (e.g. `ctrl+m/l model effort`) without forcing\n * callers into a parallel struct or a custom renderer.\n */\n extra?: {\n key: string\n label: string\n /**\n * Dispatchable binding spec behind `extra.key` when the displayed\n * chord is an abbreviation (`/l` for `ctrl+l` — see `shortChord`).\n * The abbreviation itself doesn't parse as a keypress, so a renderer\n * that makes the secondary chord independently clickable needs the\n * full spec. Defaults to `extra.key` when omitted.\n */\n keySpec?: string\n /** Defaults to the primary key's color on the renderer side: `ctrl+m/l` reads as one binding. */\n keyColor?: string\n /** Defaults to dim on the renderer side. */\n labelColor?: string\n }\n}\n\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * @param text - Text to shorten.\n * @param max - Maximum length in characters, ellipsis included.\n */\nexport function truncateTrailing(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max === 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/** Blank columns between two hints in a row. */\nexport const HINT_GAP = 2\n\n/**\n * Column layout of one hint, from its first cell:\n *\n * `ctrl+m/l` ` gpt-5` ` high fast`\n * └ keys ──┘ └label─┘ └extra label┘\n *\n * `key` then `extra.key`, a space, the label; an empty key with no extra\n * drops the space (a label-only hint). The TUI's click zones and the width\n * math below both read this, so they can't drift apart.\n */\nexport interface HintLayout {\n /** Columns `[extraKeyStart, extraKeyEnd)` hold `extra.key` (empty range without one). */\n extraKeyStart: number\n extraKeyEnd: number\n /** First column of the space before `extra.label`; equals `width` without an extra. */\n extraLabelStart: number\n /** Total columns. */\n width: number\n}\n\n/**\n * Layout of a single hint, see {@link HintLayout}. Pure / total.\n *\n * @param h - Hint to measure.\n */\nexport function hintLayout(h: Hint): HintLayout {\n const extraKeyStart = h.key.length\n const extraKeyEnd = extraKeyStart + (h.extra?.key.length ?? 0)\n const labelEnd = extraKeyEnd + (extraKeyEnd > 0 && h.label !== '' ? 1 : 0) + h.label.length\n return {\n extraKeyStart,\n extraKeyEnd,\n extraLabelStart: labelEnd,\n width: labelEnd + (h.extra ? 1 + h.extra.label.length : 0),\n }\n}\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered as a\n * hint row (keys, labels, {@link HINT_GAP} between hints). Exported so\n * prompt-box overlays can run the same responsive math as the bottom-bar\n * footer when deciding whether trigger hints fit. Pure / total.\n *\n * @param hints - Hints to measure.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n return hints.reduce((sum, h, i) => sum + hintLayout(h).width + (i > 0 ? HINT_GAP : 0), 0)\n}\n\n/**\n * Stable empty list so callers can compare by reference. Exported so\n * any consumer that needs to feed \"no primary hints\" into the same\n * `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's\n * minimal UI mode, where the prompt overlay shows only `@` / `/`\n * triggers) reuses the same frozen array instead of allocating a new\n * one each render.\n */\nexport const EMPTY_HINTS: readonly Hint[] = Object.freeze([])\n\n/**\n * Return the longest prefix of `hints` whose rendered width fits within\n * `budget`. Used to degrade hint rows gracefully at narrow terminal\n * widths instead of letting an absolutely-positioned hint row wrap\n * mid-segment (which paints the overflow over surrounding borders and\n * looks like garbled glyphs in a TUI).\n *\n * Prefix-only (no reordering, no last-hint priority) so the survivors\n * keep their authored order — the user's muscle memory for \"leftmost\n * hint = primary action\" stays intact as the terminal shrinks.\n *\n * @param hints - Hints in display order.\n * @param budget - Available width in characters.\n */\nexport function clipHintsToWidth(hints: readonly Hint[], budget: number): readonly Hint[] {\n if (budget <= 0 || hints.length === 0)\n return EMPTY_HINTS\n const out: Hint[] = []\n let used = 0\n for (let i = 0; i < hints.length; i++) {\n const h = hints[i]!\n const cost = (i > 0 ? HINT_GAP : 0) + hintLayout(h).width\n if (used + cost > budget)\n break\n out.push(h)\n used += cost\n }\n if (out.length === 0)\n return EMPTY_HINTS\n return out.length === hints.length ? hints : out\n}\n","/**\n * The React side of interactions: a FIFO queue of pending requests with\n * stable actions, shared by the TUI and GUI. The protocol (tools, requests,\n * serialization, pending-state derivation) lives in `interactions-protocol.ts`\n * and is re-exported here.\n */\nimport type { ReactNode } from 'react'\nimport type { InteractionResponse, InteractionsActions, PendingInteractionEntry } from './interactions-protocol'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\nexport * from './interactions-protocol'\n\n// ---------------------------------------------------------------------------\n// React context — FIFO queue of pending requests + resolver callbacks.\n//\n// Two contexts on purpose (mirrors `safe-mode-context.tsx`):\n// - `InteractionsQueueContext` carries the live queue. Consumers re-render\n// on every push/pop.\n// - `InteractionsActionsContext` carries `{ enqueue, resolveHead, … }` and\n// never changes identity, so callbacks that depend on these don't\n// re-bind on every queue churn.\n// ---------------------------------------------------------------------------\n\nconst InteractionsQueueContext = createContext<readonly PendingInteractionEntry[]>([])\nconst InteractionsActionsContext = createContext<InteractionsActions | null>(null)\n\n/**\n * React provider that holds the FIFO queue of pending interactions. Pending entries are cancelled when it unmounts.\n *\n * @param props - Component props.\n * @param props.children - Subtree that can read the queue and actions.\n */\nexport function InteractionsProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly PendingInteractionEntry[]>([])\n\n // Mirror the live queue so the unmount cleanup can settle whatever is\n // pending without re-subscribing the effect on every queue change.\n const queueRef = useRef<readonly PendingInteractionEntry[]>([])\n queueRef.current = queue\n\n // Provider unmount is a teardown path too: React drops the queue state\n // outright, so any in-flight `requestInteraction` Promise would hang\n // forever (its tool body never settles → the run's `running` gate never\n // clears). Cancel every pending entry on unmount so the resolver can't be\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const entry of queueRef.current)\n entry.cancel('Interactions provider unmounted')\n }, [])\n\n const enqueue = useCallback((entry: PendingInteractionEntry) => {\n setQueue(prev => [...prev, entry])\n }, [])\n\n const resolveHead = useCallback((response: InteractionResponse) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(response)\n return rest\n })\n }, [])\n\n const cancelHead = useCallback((reason?: string) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.cancel(reason)\n return rest\n })\n }, [])\n\n const cancelAll = useCallback((reason?: string) => {\n setQueue((prev) => {\n for (const entry of prev) entry.cancel(reason)\n return []\n })\n }, [])\n\n const dropById = useCallback((requestId: string) => {\n // Remove WITHOUT settling: the daemon already resolved this in the\n // window that answered it. Neither resolve nor cancel — both would\n // reach back toward the (no-op) daemon callback needlessly.\n setQueue(prev => prev.filter(entry => entry.request.id !== requestId))\n }, [])\n\n // Stable actions identity — every member is `useCallback`'d with `[]`\n // deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useInteractionsActions()`\n // never re-render purely because of queue state.\n const actionsRef = useRef<InteractionsActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { enqueue, resolveHead, cancelHead, cancelAll, dropById }\n\n return (\n <InteractionsActionsContext.Provider value={actionsRef.current}>\n <InteractionsQueueContext.Provider value={queue}>\n {children}\n </InteractionsQueueContext.Provider>\n </InteractionsActionsContext.Provider>\n )\n}\n\n/** Read the live queue (re-renders on every push/pop). */\nexport function useInteractionsQueue(): readonly PendingInteractionEntry[] {\n return useContext(InteractionsQueueContext)\n}\n\n/** Read the stable actions object. */\nexport function useInteractionsActions(): InteractionsActions {\n const ctx = useContext(InteractionsActionsContext)\n if (!ctx)\n throw new Error('useInteractionsActions must be used inside <InteractionsProvider>')\n return ctx\n}\n","/**\n * React provider for the MCP per-server OAuth state map.\n *\n * `ModalRoot.open(node)` captures the modal contents as a snapshot — naive\n * prop-passing would freeze the auth badge at whatever status existed when\n * the user opened the picker, and a `mcp:auth:url` arriving mid-login\n * would never reach the rendered modal. Routing the state through a\n * context provider mounted ABOVE `ModalRoot` fixes that: a state update\n * re-renders the provider, which re-renders `ModalRoot`, which re-renders\n * the (referentially-stable) modal child node — and consumers calling\n * `useMcpAuthState()` see the fresh value.\n *\n * Two contexts on purpose:\n *\n * - `StateContext` carries the live map. Consumers re-render on every\n * auth event.\n * - `DispatchContext` carries the reducer entry point. Identity is stable\n * across renders, so AppShell's hook handlers don't re-bind when the\n * state changes — they grab `dispatch` once and fire events for the\n * lifetime of the agent.\n *\n * Actions (login / logout / cancel) deliberately do NOT live in context —\n * they depend on AppShell-scoped resources (credential store, catalog ref,\n * agent ref), close over those via refs, and stay stable across renders.\n * Passing them as modal props at `modal.open` time is fine because the\n * captured reference reads from refs internally; the snapshot doesn't go\n * stale even though it was taken at open time.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpAuthEvent, McpAuthStateMap } from './mcp-auth-state'\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { reduceMcpAuth } from './mcp-auth-state'\n\nconst StateContext = createContext<McpAuthStateMap>({})\nconst DispatchContext = createContext<((event: McpAuthEvent) => void) | null>(null)\n\n/**\n * Mount once near the top of the React tree (above `ModalRoot`). Children\n * use `useMcpAuthState()` to read and `useMcpAuthDispatch()` to fire events.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function McpAuthProvider({ children }: { children: ReactNode }) {\n const [state, setState] = useState<McpAuthStateMap>({})\n const dispatch = useCallback((event: McpAuthEvent) => {\n setState(prev => reduceMcpAuth(prev, event))\n }, [])\n return (\n <StateContext.Provider value={state}>\n <DispatchContext.Provider value={dispatch}>\n {children}\n </DispatchContext.Provider>\n </StateContext.Provider>\n )\n}\n\n/** Returns the current auth state map, keyed by server name. */\nexport function useMcpAuthState(): McpAuthStateMap {\n return useContext(StateContext)\n}\n\n/** Returns the function that applies an {@link McpAuthEvent} to the auth state. Throws when used outside {@link McpAuthProvider}. */\nexport function useMcpAuthDispatch(): (event: McpAuthEvent) => void {\n const dispatch = useContext(DispatchContext)\n if (!dispatch)\n throw new Error('useMcpAuthDispatch must be used inside <McpAuthProvider>')\n return dispatch\n}\n","/**\n * Renderer-agnostic data model for the MCP settings list when it\n * supports per-server expand / collapse with nested tool rows.\n *\n * Lives in `chat/` (not `tui/`) because both the standalone\n * `McpsSettingsModal` and the inline `<McpsList>` inside the unified\n * `SettingsModal` consume the exact same flattening + cursor\n * arithmetic — re-deriving it in two places would let the two\n * surfaces drift on edge cases (empty tool catalogs, freshly-removed\n * servers, stale expansion state).\n *\n * The model is a flat `VisibleMcpRow[]` so renderers can iterate once,\n * and the cursor is a single integer that walks both kinds (server\n * headers AND tool checkboxes) without separate \"which server am I\n * on / which tool inside it\" state.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { DiscoveredMcp } from './mcps-discovery'\n\n/**\n * One row in the flattened MCP settings list. Three kinds:\n *\n * - `'server'` — server header with the chevron + checkbox +\n * transport detail. Always present.\n * - `'tool'` — checkbox + description for a tool advertised\n * by the parent server. Present only when the\n * server is expanded AND its tool cache is\n * populated.\n * - `'tool-empty'` — placeholder shown when an expanded server\n * has no cached tool list yet (first launch\n * before any session has bootstrapped the\n * server). Lets the renderer surface a\n * one-line \"send a prompt to populate\" hint\n * WITHOUT changing the server's expand\n * affordance.\n */\nexport type VisibleMcpRow\n = | { kind: 'server', entry: DiscoveredMcp }\n | { kind: 'tool', serverName: string, tool: McpToolSchema }\n | { kind: 'tool-empty', serverName: string }\n\n/**\n * Flatten a server catalog plus the per-server expanded set into the\n * visible-row list a renderer cycles through.\n *\n * - `catalog` — filtered / ordered server list (post-search,\n * post-allow-list).\n * - `expanded` — names of servers whose tool list is open.\n * Servers not in this set render as a single\n * server-header row.\n * - `toolsByServer` — per-server full tool catalog (from\n * `chat/mcp-tools-cache.ts`). Missing key →\n * `'tool-empty'` placeholder under the\n * expanded server.\n *\n * @param catalog - Discovered servers in display order.\n * @param expanded - Names of servers whose tool rows are shown.\n * @param toolsByServer - Cached tool lists keyed by server name. A missing or empty list yields a `tool-empty` row.\n */\nexport function buildVisibleMcpRows(\n catalog: readonly DiscoveredMcp[],\n expanded: ReadonlySet<string>,\n toolsByServer: Record<string, readonly McpToolSchema[]>,\n): VisibleMcpRow[] {\n const out: VisibleMcpRow[] = []\n for (const entry of catalog) {\n out.push({ kind: 'server', entry })\n if (!expanded.has(entry.config.name))\n continue\n const tools = toolsByServer[entry.config.name] ?? []\n if (tools.length === 0) {\n out.push({ kind: 'tool-empty', serverName: entry.config.name })\n continue\n }\n for (const tool of tools)\n out.push({ kind: 'tool', serverName: entry.config.name, tool })\n }\n return out\n}\n\n/**\n * Server name a row \"belongs to\" for catalog / toggle / auth lookups.\n * For a server row that's its own name; for tool / tool-empty rows\n * it's the parent server's name.\n *\n * @param row - Any visible row.\n */\nexport function parentServerName(row: VisibleMcpRow): string {\n return row.kind === 'server' ? row.entry.config.name : row.serverName\n}\n\n/**\n * Index in `rows` of the server header for the given parent name. `-1`\n * when not present. Used by collapse handlers to snap the cursor up\n * to the parent BEFORE shrinking the row list — otherwise the cursor\n * would land on whichever sibling row now occupies the same flat\n * index.\n *\n * @param rows - The visible rows.\n * @param parentName - Server name to look for.\n */\nexport function indexOfServerRow(rows: readonly VisibleMcpRow[], parentName: string): number {\n return rows.findIndex(\n r => r.kind === 'server' && r.entry.config.name === parentName,\n )\n}\n","/**\n * Renderer-agnostic per-tool toggle for MCP servers. Sibling of\n * {@link useEnabledToggleSet} — same shape, opposite polarity:\n *\n * - `useEnabledToggleSet` is an **allowlist** (`enabledMcps` / etc.),\n * `undefined` = everything enabled, toggle moves to an explicit list.\n * - `useMcpToolToggleSet` is a **deny-list** (`disabledMcpTools`),\n * missing entry = everything enabled, toggle moves to an explicit\n * list of NAMES TO HIDE.\n *\n * The polarity flip is intentional and documented on the setting itself\n * (`Settings.disabledMcpTools`): when an MCP server ships a new tool in\n * a later release, a user who has already opted out of `big_tool` still\n * gets the new one — opposite preference from server-level toggles\n * where a new server should NOT silently appear in the catalog.\n *\n * Two hook flavors:\n *\n * - {@link useMcpToolToggleSet} — one server. Convenience for hosts\n * that render a single server's tool list at a time.\n * - {@link useMcpToolToggleMap} — every server's toggle handler in\n * one call. Required by hosts that dispatch toggles from a shared\n * keyboard handler at the top of the tree (`mcps-settings.tsx`)\n * where calling the single-server hook in a loop would violate\n * rules-of-hooks the moment a server is added or removed.\n *\n * Both flavors route through the same {@link buildToolToggle} factory\n * so semantics stay byte-identical regardless of entry point.\n *\n * Garbage-collects on every write: stale names (tools that disappeared\n * from the server's catalog between launches) are dropped so the\n * persisted deny-list doesn't grow unbounded. Same invariant as the\n * sibling hook for `enabledX` allowlists.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { Settings } from './types'\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Per-tool enable state and toggle handler for one MCP server. */\nexport interface McpToolToggleSet {\n /** Live set of ENABLED tool names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Flip one tool on/off; persists immediately via `setSetting`. */\n toggle: (toolName: string) => void\n}\n\n/**\n * Bind a single MCP server's per-tool toggle to\n * `Settings.disabledMcpTools[serverName]`.\n *\n * `catalog` is the FULL list of tools the server advertises (i.e. the\n * cached `listTools()` payload from `chat/mcp-tools-cache.ts`). The\n * hook needs it both to render `enabledSet` (every catalog tool not in\n * the persisted deny-list) and to garbage-collect stale deny-list\n * entries on every write.\n *\n * @param opts - The server name and its full tool catalog.\n */\nexport function useMcpToolToggleSet(opts: {\n serverName: string\n catalog: readonly McpToolSchema[]\n}): McpToolToggleSet {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n\n const catalogNames = useMemo(() => opts.catalog.map(t => t.name), [opts.catalog])\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(\n () => buildToolToggle({\n serverName: opts.serverName,\n catalogNames,\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n }),\n [opts.serverName, catalogNames, persistedDenyAll, setDisabled],\n )\n}\n\n/**\n * Bind every server in `catalogByServer` to its own per-tool toggle in\n * a single hook call. Returns a `serverName → McpToolToggleSet` map.\n *\n * Use this when a host needs handlers for multiple servers at once\n * (the TUI's MCP settings panel keyboard dispatcher is the canonical\n * example) — calling {@link useMcpToolToggleSet} in a `for` / `.map`\n * over a dynamic catalog would change the hook-call order whenever a\n * server is added or removed, violating React's rules-of-hooks. This\n * hook calls `useMemo` exactly once regardless of server count.\n *\n * Servers absent from `catalogByServer` get no entry in the result.\n * The persisted deny-list still keeps their entries on disk — they\n * just stay invisible to this consumer until the catalog re-includes\n * them.\n *\n * @param opts - Tool catalogs keyed by server name.\n */\nexport function useMcpToolToggleMap(opts: {\n catalogByServer: Record<string, readonly McpToolSchema[]>\n}): Record<string, McpToolToggleSet> {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(() => {\n const out: Record<string, McpToolToggleSet> = {}\n for (const [serverName, catalog] of Object.entries(opts.catalogByServer)) {\n out[serverName] = buildToolToggle({\n serverName,\n catalogNames: catalog.map(t => t.name),\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n })\n }\n return out\n }, [opts.catalogByServer, persistedDenyAll, setDisabled])\n}\n\n/**\n * Pure factory shared by both hook flavors. Captures `persistedDenyAll`\n * + `setDisabledMcpTools` at the call site so toggle handlers stay\n * referentially stable across renders that don't touch the underlying\n * settings entry — important for `useKeyboard` to avoid re-binding on\n * every catalog change.\n *\n * Exported for direct use by non-React consumers (CLI, GUI shells)\n * that want the same write semantics without the hook plumbing.\n *\n * @param args - Server name, the server's catalog tool names, the persisted deny-list for all servers and the setter that writes it.\n */\nexport function buildToolToggle(args: {\n serverName: string\n catalogNames: readonly string[]\n persistedDenyAll: Settings['disabledMcpTools']\n setDisabledMcpTools: (next: Settings['disabledMcpTools']) => void\n}): McpToolToggleSet {\n const { serverName, catalogNames, persistedDenyAll, setDisabledMcpTools } = args\n const persistedDeny = persistedDenyAll?.[serverName]\n const catalogSet = new Set(catalogNames)\n\n const enabledSet: ReadonlySet<string> = (() => {\n if (!persistedDeny || persistedDeny.length === 0)\n return new Set(catalogNames)\n const deny = new Set(persistedDeny)\n return new Set(catalogNames.filter(name => !deny.has(name)))\n })()\n\n const toggle = (toolName: string): void => {\n // Compute the next deny-list against the persisted state (NOT\n // `enabledSet`) so we never accidentally promote \"implicit-enabled-\n // because-no-entry\" tools into an explicit list on the first\n // toggle. GC stale names at the same time so the persisted file\n // tracks reality.\n const currentDeny = new Set(persistedDeny ?? [])\n if (currentDeny.has(toolName))\n currentDeny.delete(toolName)\n else\n currentDeny.add(toolName)\n\n const nextForServer = [...currentDeny]\n .filter(n => catalogSet.has(n))\n .sort()\n\n const prevMap = persistedDenyAll ?? {}\n const nextMap: Record<string, readonly string[]> = { ...prevMap }\n if (nextForServer.length === 0) {\n // Empty entry collapses back to \"no customization\" so the\n // persisted file stays tidy when a user re-enables every tool\n // they previously disabled.\n delete nextMap[serverName]\n }\n else {\n nextMap[serverName] = nextForServer\n }\n\n // Top-level `undefined` represents \"no per-tool prefs anywhere\".\n // Restore that when the last server entry empties out so the\n // diff against defaults stays minimal.\n const next: Settings['disabledMcpTools']\n = Object.keys(nextMap).length === 0 ? undefined : nextMap\n setDisabledMcpTools(next)\n }\n\n return { enabledSet, toggle }\n}\n","/**\n * Manual OAuth redirect-URL handoff.\n *\n * Users who run zidane over SSH (or behind a proxy / firewall that blocks\n * loopback) can't have the local browser hit the in-process callback server.\n * They CAN, however, copy the URL their browser was redirected to —\n * `http://127.0.0.1:<port>/callback?code=...&state=...` — and paste it back\n * into the TUI.\n *\n * The trick: that URL IS the callback our local server is listening on. We\n * just `fetch()` it ourselves. The server runs in the same process; the\n * request hits its handler exactly as if a real browser had arrived, the\n * OAuth promise (`waitForCode` for pi-ai providers, `startOAuthCallback` for\n * MCP) resolves through the normal happy path, and the upstream flow\n * continues uninterrupted. No new code path inside the OAuth state machine.\n *\n * Defense in depth: we reject anything that isn't a loopback URL — fetching\n * an arbitrary user-pasted URL from inside the agent process would be a\n * trivial SSRF.\n */\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])\n\n/**\n * The `redirect_uri` the authorization server was told to come back to,\n * read out of the authorization URL itself. That's our loopback callback\n * (`http://127.0.0.1:<port>/callback`) — the UI shows it so an SSH user\n * knows which address to fish out of the browser's URL bar, and\n * {@link normalizeRedirectPaste} uses it to rebuild a full callback URL\n * from a partial paste.\n *\n * Returns `null` when the URL doesn't parse or carries no usable\n * `redirect_uri` — callers degrade to \"paste the whole URL\".\n *\n * @param authUrl - The authorization URL to read.\n */\nexport function redirectUriFromAuthUrl(authUrl: string): URL | null {\n let parsed: URL\n try {\n parsed = new URL(authUrl)\n }\n catch {\n return null\n }\n const redirect = parsed.searchParams.get('redirect_uri')\n if (!redirect)\n return null\n try {\n return new URL(redirect)\n }\n catch {\n return null\n }\n}\n\n/**\n * Widen what counts as an acceptable paste. The happy path is the full\n * redirect URL out of the browser's address bar, but users on SSH\n * routinely end up holding less than that: some authorization servers\n * render the code on the page instead of redirecting, and copying from a\n * \"can't reach this site\" error page loses everything but the query.\n *\n * Accepted forms, given the authorization URL for context:\n *\n * - `http://127.0.0.1:5123/callback?code=x&state=y` — passthrough (any\n * absolute URL is returned untouched; the loopback guard in\n * {@link fetchOAuthRedirect} still has final say, so a pasted\n * `https://evil.test` is rejected there, not silently rewritten).\n * - `/callback?code=x&state=y` — path + query onto the callback origin.\n * - `?code=x&state=y` / `code=x&state=y` — query onto the callback URL.\n * - `x` — a bare authorization code; `state` is taken from the\n * authorization URL when it carried one (the loopback server forwards\n * `state` verbatim and the SDK owns verification, so a missing one is\n * not fatal). Anything with whitespace inside is prose (an error page\n * fragment), not a code — refused with a clearer error than a server\n * 400 would give.\n *\n * Throws when the input can't be turned into a URL — no `authUrl` context\n * for a partial paste, or an unusable authorization URL.\n *\n * @param pasted - What the user pasted: a full URL, a path, a query string or a bare code.\n * @param authUrl - The authorization URL on screen. Required to complete anything shorter than a full URL.\n */\nexport function normalizeRedirectPaste(pasted: string, authUrl?: string): string {\n const trimmed = pasted.trim()\n if (!trimmed)\n throw new Error('Paste the redirect URL from your browser.')\n // Absolute URL → caller's problem to validate. Checked by scheme rather\n // than `new URL()` success so a bare code like `abc` (which URL happily\n // rejects) and a scheme-ful `mcp://…` are told apart deterministically.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed))\n return trimmed\n\n const callback = authUrl ? redirectUriFromAuthUrl(authUrl) : null\n if (!callback) {\n throw new Error(\n 'That doesn\\'t look like a URL. Paste the full address your browser tried to reach (starting with http://127.0.0.1).',\n )\n }\n\n // `/callback?code=…` — keep the pasted path, it may differ from the one\n // we registered (a server that normalized a trailing slash, say). Same\n // outcome guard as the query branch below: a bare `/callback` carries no\n // OAuth result, and fetching it would only produce an opaque server 400\n // where a targeted message tells the user exactly what to re-copy.\n if (trimmed.startsWith('/')) {\n const merged = new URL(trimmed, callback)\n if (!merged.searchParams.has('code') && !merged.searchParams.has('error'))\n throw new Error('That path has no \"code\" parameter. Paste the full address from your browser\\'s URL bar.')\n return merged.toString()\n }\n\n const target = new URL(callback.toString())\n const query = trimmed.startsWith('?') ? trimmed.slice(1) : trimmed\n // A bare authorization code is a single URL-legal token — including\n // base64 padding, so trailing `=` stays code-like (`abc==`). Any OTHER\n // placement of `=` means key/value pairs (`foo=bar`, `state=…&code=…`):\n // treat as a query string, never as a code — URL-encoding `foo=bar` into\n // `code=foo%3Dbar` would fire a doomed request where a targeted error\n // tells the user what to re-copy.\n const codeLike = /^[^=\\s]+=*$/.test(query)\n if (!codeLike && query.includes('=')) {\n const params = new URLSearchParams(query)\n if (!params.has('code') && !params.has('error'))\n throw new Error('That fragment has no \"code\" parameter. Paste the full address from your browser, or just the code.')\n for (const [key, value] of params)\n target.searchParams.set(key, value)\n return target.toString()\n }\n\n // Bare code. Whitespace-bearing pastes never reach here (`codeLike`\n // excludes them) except as pure prose without `=`, so keep the explicit\n // reject for those: sending `code=access%20denied` to the callback\n // server would 400 without explaining anything.\n if (/\\s/.test(query))\n throw new Error('That doesn\\'t look like an authorization code. Paste the full address from your browser, or just the code itself.')\n target.searchParams.set('code', query)\n const state = new URL(authUrl!).searchParams.get('state')\n if (state)\n target.searchParams.set('state', state)\n return target.toString()\n}\n\n/** Outcome of calling the loopback callback server. */\nexport interface FetchOAuthRedirectResult {\n /** Status code from the callback server. 2xx = success. */\n status: number\n /** Best-effort message extracted from the response body (HTML title or trimmed text). */\n message?: string\n}\n\n/**\n * Treat `pasted` as a callback-URL paste. Validates it's a loopback URL,\n * fires a GET, returns the status.\n *\n * Pass `authUrl` (the authorization URL currently on screen) to accept\n * partial pastes — a bare code, a query string, a path — via\n * {@link normalizeRedirectPaste}. Without it only a full absolute URL is\n * accepted.\n *\n * Throws when:\n * - `pasted` doesn't parse as a URL (and can't be completed from `authUrl`).\n * - The URL host isn't loopback (rejects SSRF).\n * - The fetch errors out (network, timeout).\n *\n * Returns success/failure for non-2xx responses; the caller decides whether\n * a 4xx is fatal (state mismatch is a 400 the user can retry from).\n *\n * @param pasted - What the user pasted: a callback URL, or a partial one when `options.authUrl` is set.\n * @param options - Abort signal, request timeout in milliseconds (`5000` by default) and the authorization URL used to complete partial pastes.\n */\nexport async function fetchOAuthRedirect(\n pasted: string,\n options: { signal?: AbortSignal, timeoutMs?: number, authUrl?: string } = {},\n): Promise<FetchOAuthRedirectResult> {\n const trimmed = normalizeRedirectPaste(pasted, options.authUrl)\n\n let url: URL\n try {\n url = new URL(trimmed)\n }\n catch {\n throw new Error('That doesn\\'t look like a URL. Paste the full address from your browser.')\n }\n\n // Strip brackets that some browsers add around IPv6 hosts so the\n // comparison hits LOOPBACK_HOSTS regardless of textual form.\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n `Expected a loopback URL (127.0.0.1 / localhost), got \"${url.hostname}\". The browser should have redirected to a localhost address.`,\n )\n }\n\n // Short timeout: the callback server is in-process, so any meaningful\n // delay means it's gone (closed by a prior abort) or never started.\n const timeoutMs = options.timeoutMs ?? 5000\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n options.signal?.addEventListener('abort', () => ac.abort(), { once: true })\n\n let response: Response\n try {\n response = await fetch(url.toString(), { signal: ac.signal })\n }\n catch (err) {\n if ((err as Error).name === 'AbortError')\n throw new Error('No response from the local callback server. Was the login cancelled?')\n throw new Error(`Could not reach the local callback server: ${(err as Error).message}`)\n }\n finally {\n clearTimeout(timer)\n }\n\n const bodyText = await response.text().catch(() => '')\n return { status: response.status, message: extractMessage(bodyText) }\n}\n\n/**\n * Pick the most useful one-line message out of the callback server's HTML\n * response. pi-ai uses `<h1>` for the heading; the MCP callback uses\n * a leading `<p>`. We try both, then fall back to a stripped snippet.\n */\nfunction extractMessage(html: string): string | undefined {\n if (!html)\n return undefined\n const h1 = /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i.exec(html)?.[1]\n if (h1)\n return stripTags(h1).trim() || undefined\n const p = /<p[^>]*>([\\s\\S]*?)<\\/p>/i.exec(html)?.[1]\n if (p)\n return stripTags(p).trim() || undefined\n return undefined\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#39;/g, '\\'')\n}\n","/**\n * The calls still running in a transcript read from disk. The assistant turn\n * holding a step's calls is saved before they run, their results (with\n * `durationMs`) once all of them end, so a page read mid-call shows a call\n * with neither a result nor a start. The host's running work\n * (`chat.listWork`, with `children` for the subagents' calls) has the\n * start: stamped back on, the call's row counts from when it began, not\n * from when the page was read.\n *\n * Shared by the TUI's attach and the desktop app's transcript pages.\n */\nimport type { RunningWorkItem } from './host/signals'\nimport type { StreamEvent } from './types'\n\n/** What these read off an event: a `StreamEvent`, or a client's copy of one. */\ntype CallEvent = Pick<StreamEvent, 'kind' | 'callId' | 'childId' | 'startedAt' | 'resuming'>\n\n/** A call by its agent and id: ids are only unique within one agent. */\nfunction callKey(callId: string, childId: string | undefined): string {\n return `${childId ?? ''}\\u0000${callId}`\n}\n\n/**\n * Whether a call, a subagent's included, has neither a result nor a start: a\n * call that may still be running, untimed. A page without one needs no\n * running work.\n *\n * @param events - The page's events.\n */\nexport function hasUntimedOpenCalls(events: readonly CallEvent[]): boolean {\n const done = new Set(events.flatMap(e => (e.kind === 'tool-result' && e.callId ? [callKey(e.callId, e.childId)] : [])))\n return events.some(e => e.kind === 'tool' && e.callId !== undefined && e.startedAt === undefined && !done.has(callKey(e.callId, e.childId)))\n}\n\n/**\n * `events` with each running call's start from the host's running work, a\n * subagent's call matched by its `childId`, and its `resuming` mark (a\n * call of a batch a durable run resumed after a process death). `events`\n * itself when nothing is stamped.\n *\n * @param events - The page's events.\n * @param work - The session's running work (`chat.listWork`).\n */\nexport function withRunningCallStarts<E extends CallEvent>(events: E[], work: readonly Pick<RunningWorkItem, 'kind' | 'id' | 'startedAt' | 'childId' | 'resuming'>[]): E[] {\n const running = new Map(work.flatMap(item => (item.kind === 'tool' ? [[callKey(item.id, item.childId), item] as const] : [])))\n if (running.size === 0)\n return events\n let stamped = false\n const out = events.map((event) => {\n const item = event.kind === 'tool' && event.callId ? running.get(callKey(event.callId, event.childId)) : undefined\n const startedAt = item && event.startedAt === undefined ? item.startedAt : undefined\n const resuming = item?.resuming === true && event.resuming !== true\n if (startedAt === undefined && !resuming)\n return event\n stamped = true\n return { ...event, ...(startedAt !== undefined ? { startedAt } : {}), ...(resuming ? { resuming: true as const } : {}) }\n })\n return stamped ? out : events\n}\n","/**\n * Safe-mode React context — bridges the agent's `tool:gate` / `mcp:tool:gate`\n * hooks (which run inside the harness loop) with the TUI's React tree (which\n * renders the approval picker).\n *\n * Flow:\n * 1. Hook handler in `tui/host/host-session-callbacks.ts` calls `requestApproval(tool, input)`.\n * 2. Context appends a pending entry to the queue, returns a Promise.\n * 3. Picker renders the queue's head, calls `resolve(decision)` on pick.\n * 4. Head is shifted off the queue; hook handler proceeds with the decision.\n *\n * The queue is FIFO so parallel tool calls prompt in arrival order.\n *\n * Two contexts on purpose:\n * - `SafeModeQueueContext` carries the live queue. Consumers re-render on\n * every push/pop.\n * - `SafeModeActionsContext` carries `{ requestApproval, resolveHead,\n * denyAll, dropByCorrelation }` and never changes identity, so callbacks that depend on\n * these don't re-bind every time a tool call enters or leaves the queue.\n */\n\nimport type { ReactNode } from 'react'\nimport type { ModelApproval } from './types'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\n/**\n * Outcome of an approval prompt. Four bulk decisions match the original\n * single-edit modal contract; `partial` is the multi-edit branch — the\n * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.\n *\n * `partial.mask` is 1:1 with the call's hunks (in input order): `true`\n * means apply, `false` means deny. The gate handler walks the mask to\n * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the\n * approved subset and the renderer reconstructs the full per-hunk view\n * from the `<edit-outcomes>` annotation appended to the tool result.\n */\nexport type ApprovalDecision\n = | 'accept-once'\n | 'accept-session'\n | 'accept-safelist'\n | 'deny'\n | { kind: 'partial', mask: readonly boolean[] }\n\n/**\n * Identifies the caller behind an approval prompt — the parent agent or\n * a specific subagent (`child-N`). The modal's right-side title pins the\n * label so the user knows which agent issued the call when subagents\n * bubble their gates up through the parent's hook bus.\n */\nexport type ApprovalOriginator\n = | { kind: 'parent' }\n | { kind: 'child', label: string }\n\n/** A tool call waiting for the user's decision. */\nexport interface ApprovalRequest {\n /** Unique id of the request. */\n id: string\n /** Name of the tool awaiting approval. */\n tool: string\n /** The tool call's input. */\n input: Record<string, unknown>\n /** Settles the request with the user's decision. */\n resolve: (decision: ApprovalDecision) => void\n /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */\n originator?: ApprovalOriginator\n /** Model decision approvals: the decision model's verdict when it handed this call to you (`modelConcern`). */\n concern?: ModelApproval\n /**\n * Cross-window correlation id (the daemon's approval request id) for\n * entries surfaced by the daemon runtime. Lets `dropByCorrelation`\n * retract this entry when ANOTHER window resolves the same shared\n * approval, without re-sending a decision. Absent for local-runtime\n * approvals.\n */\n correlationId?: string\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (\n tool: string,\n input: Record<string, unknown>,\n originator?: ApprovalOriginator,\n correlationId?: string,\n) => Promise<ApprovalDecision>\n\n/** Functions that drive the approval queue. */\nexport interface SafeModeActions {\n /** Request a decision; resolves once the user picks. */\n requestApproval: RequestApproval\n /** Resolve the head and shift the queue forward. */\n resolveHead: (decision: ApprovalDecision) => void\n /** Resolve all pending with `deny`. Used on abort / hard exit. */\n denyAll: () => void\n /**\n * Remove the entry whose `correlationId` matches, WITHOUT resolving it\n * (the daemon already did, in another window). No-op when absent.\n */\n dropByCorrelation: (correlationId: string) => void\n}\n\nconst SafeModeQueueContext = createContext<readonly ApprovalRequest[]>([])\nconst SafeModeActionsContext = createContext<SafeModeActions | null>(null)\n\nlet approvalIdCounter = 0\nfunction nextApprovalId(): string {\n // Monotonic id is enough — uniqueness is scoped to a single TUI session\n // and the value never leaves the React tree. Avoids Date.now/Math.random\n // collisions inside tight parallel-tool batches.\n approvalIdCounter += 1\n return `approval-${approvalIdCounter}`\n}\n\n/**\n * Owns the queue + actions. Splits the value across two contexts so a queue\n * change doesn't invalidate every callback memo that closes over the actions.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\n\n // Mirror the live queue so the unmount cleanup settles pending approvals\n // without re-subscribing on every queue change.\n const queueRef = useRef<readonly ApprovalRequest[]>([])\n queueRef.current = queue\n\n // Provider unmount drops the queue state — a tool body parked on\n // `requestApproval` would then hang forever (its run's `running` gate\n // never clears). Deny every pending request on unmount so no resolver is\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const p of queueRef.current)\n p.resolve('deny')\n }, [])\n\n // `setQueue` is stable, so `useCallback([])` here gives genuine identity\n // stability — `requestApproval` is the same function reference for the\n // lifetime of the provider, which is what hook handlers need.\n const requestApproval = useCallback<RequestApproval>(\n (tool, input, originator, correlationId) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [\n ...prev,\n {\n id: nextApprovalId(),\n tool,\n input,\n resolve,\n ...(originator ? { originator } : {}),\n ...(correlationId ? { correlationId } : {}),\n },\n ])\n }),\n [],\n )\n\n const resolveHead = useCallback((decision: ApprovalDecision) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(decision)\n return rest\n })\n }, [])\n\n const denyAll = useCallback(() => {\n setQueue((prev) => {\n for (const p of prev) p.resolve('deny')\n return []\n })\n }, [])\n\n const dropByCorrelation = useCallback((correlationId: string) => {\n // Remove WITHOUT resolving: the daemon already settled this approval\n // in the window that answered it. Resolving here would double-answer\n // (harmless deny) AND fire the RPC guard path unnecessarily.\n setQueue(prev => prev.filter(p => p.correlationId !== correlationId))\n }, [])\n\n // Actions object is stable across renders — its members are all `useCallback`'d\n // with `[]` deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useSafeModeActions()` never\n // re-render purely because of safe-mode state.\n const actionsRef = useRef<SafeModeActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { requestApproval, resolveHead, denyAll, dropByCorrelation }\n\n return (\n <SafeModeActionsContext.Provider value={actionsRef.current}>\n <SafeModeQueueContext.Provider value={queue}>\n {children}\n </SafeModeQueueContext.Provider>\n </SafeModeActionsContext.Provider>\n )\n}\n\n/** Returns the pending approval requests, oldest first. */\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\n/** Returns the approval actions. Throws when used outside {@link SafeModeProvider}. */\nexport function useSafeModeActions(): SafeModeActions {\n const ctx = useContext(SafeModeActionsContext)\n if (!ctx)\n throw new Error('useSafeModeActions must be used inside <SafeModeProvider>')\n return ctx\n}\n","import type { Dispatch, MutableRefObject, SetStateAction } from 'react'\nimport type { TranscriptWeight } from './streaming-pure'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { appendMarkdownDelta, appendThinkingLines, smoothCharsForTick, stepTranscriptCap, takeChars } from './streaming-pure'\n\n// Pure delta-merge / finalize reducers + context-window math live in the\n// node-free `./streaming-pure` leaf (importable from `zidane/chat/pure`, and\n// shared with the GUI renderer). Re-exported here so existing `zidane/chat`\n// consumers keep their import paths.\nexport { appendMarkdownDelta, appendThinkingLines, capTranscriptEvents, finalizeStreamingMarkdown, finalizeStreamingMarkdownForOwner, hiddenEarlierTranscriptCount, MAX_TRANSCRIPT_EVENTS, mergeTargetIndex, replaceTranscriptTurnEvents, revealEarlierTranscriptEvents, smoothCpsForBacklog, stepTranscriptCap, TRANSCRIPT_TRIM_STEP_EVENTS, TRANSCRIPT_TRIM_TO_EVENTS, turnContextSize } from './streaming-pure'\n\n/**\n * Default tick interval for the continuous drain loop. ~16ms aligns with\n * the 60fps renderer target so the typewriter cadence reveals 1 portion of\n * content per paint frame (a host whose frame rate differs passes\n * {@link StreamBufferOptions.getTickIntervalMs}). The bucket-derived\n * `chars-per-tick` decides how much actually lands per tick — see\n * {@link smoothCharsForTick}.\n */\nconst TICK_INTERVAL_MS = 16\n\n/**\n * Safety bound for {@link StreamBuffer.whenDrained} — per window of NO\n * drain progress. A long response legitimately types out for longer\n * than any fixed deadline (the ramp bottoms out at\n * the base rate, `SMOOTH_BASE_CPS` in `./streaming-pure`), so each committed tick re-arms the window;\n * only a genuinely wedged ticker — this long with nothing committed —\n * fires the escape hatch, and convergence (transcript resync) is never\n * blocked forever.\n */\nexport const WHEN_DRAINED_TIMEOUT_MS = 3000\n\nconst PARENT_OWNER: Owner = 'parent'\n\n/** One owner's (parent or a subagent) not-yet-committed deltas. */\ninterface DeltaBucket {\n markdown: string\n thinking: string\n owner: Owner\n depth: number\n /** Last turnId seen for this bucket. Tagged onto new events when the bucket flushes. */\n turnId?: string\n /**\n * Fractional chars carried over from the previous smooth tick. The\n * tick computes an exact float (`cps * dt`), takes the integer part,\n * and stashes the leftover here so the next tick adds it back in.\n * This makes the long-run rate match the target CPS exactly instead\n * of being inflated by `Math.ceil` rounding on every tick.\n */\n smoothCarry: number\n}\n\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth, smoothCarry: 0 }\n}\n\nfunction applyBucket(prev: StreamEvent[], bucket: DeltaBucket): StreamEvent[] {\n let result = prev\n if (bucket.thinking)\n result = appendThinkingLines(result, bucket.thinking, bucket.owner, bucket.depth, bucket.turnId)\n if (bucket.markdown)\n result = appendMarkdownDelta(result, bucket.markdown, bucket.owner, bucket.depth, bucket.turnId)\n return result\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — per-owner stream-event accumulator with two drain modes.\n//\n// Provider deltas arrive at ~50Hz; flushing each one through React causes the\n// ScrollBox to recompute layout faster than the terminal can clear and\n// repaint, producing overdraw artifacts during scroll. We buffer per owner\n// (parent + each active subagent) and drain via a continuous ticker:\n//\n// - **smooth mode** (default — gated on {@link Settings.smoothStreaming}):\n// drip-feed text character-by-character at the base rate (`./streaming-pure`), with\n// an adaptive burst when the buffer falls behind. Reads as a typewriter\n// effect and masks bursty provider chunks behind a steady cadence.\n// While markdown backlog remains, turn-boundary calls (`appendImmediate`\n// / `flushAndUpdate`) queue behind it and `flush` leaves the ticker to\n// finish. They apply once the backlog has typed out.\n// - **batched mode** (off): drain everything per tick.\n//\n// `getSmooth` is called on every tick so toggling the user setting at\n// runtime takes effect on the next drain without rebuilding the buffer.\n// ---------------------------------------------------------------------------\n\n/** Which agent and turn a streaming delta came from. */\nexport interface StreamSource {\n /** Pass `undefined` / omit for parent-agent events. */\n childId?: string\n /** Nesting depth — 0 for parent, ≥ 1 for subagents. */\n depth?: number\n /**\n * `SessionTurn.id` this delta belongs to. Tagged onto new events so the\n * TUI's select-turn mode can group every event from one turn. Stored on\n * the active bucket and refreshed on every delta so a turn boundary\n * (`turn:after` flushes the bucket) doesn't carry stale ids forward.\n */\n turnId?: string\n}\n\n/** Handle returned by {@link useStreamBuffer}. */\nexport interface StreamBuffer {\n /** Queue a streaming delta for the next flush tick. */\n queueStreamDelta: (kind: 'markdown' | 'thinking', delta: string, source?: StreamSource) => void\n /** Drain pending deltas immediately, then append a non-streaming event. */\n appendImmediate: (evt: StreamEvent) => void\n /** Drain pending deltas immediately, then transform the event list. */\n flushAndUpdate: (update: (events: StreamEvent[]) => StreamEvent[]) => void\n /** Drain pending deltas without further transformation. */\n flush: () => void\n /** Cancel any pending flush and drop buffered deltas (on session teardown). */\n reset: () => void\n /**\n * Resolves once the pacer is idle — no bucket holds queued content and\n * no post-drain updater is pending. Resolves immediately when already\n * idle (the batched / `smoothStreaming: false` path never queues, so\n * this is zero-cost there). A safety timeout (default\n * {@link WHEN_DRAINED_TIMEOUT_MS}) resolves anyway if the ticker\n * wedges — measured as a full window with NO drain progress, so a\n * long response that keeps typing re-arms it and never snaps — and\n * callers gating a transcript convergence on the drain can never\n * dead-lock. `reset()` also releases every waiter — the backlog it\n * drops would otherwise never drain.\n */\n whenDrained: (timeoutMs?: number) => Promise<void>\n}\n\n/** Options for {@link useStreamBuffer}. */\nexport interface StreamBufferOptions {\n /**\n * Returns `true` to drip-feed buffered text at the smooth typewriter\n * cadence, `false` to drain everything per tick (batched mode). Called on every tick, so flipping the user setting\n * takes effect on the next drain.\n */\n getSmooth?: () => boolean\n /**\n * Display-memory bound applied on every drain commit (see\n * {@link capTranscriptEvents}): above this many events the oldest are\n * trimmed at a turn boundary and replaced with a cumulative\n * \"… N earlier items hidden\" marker event, at most\n * {@link TRANSCRIPT_TRIM_STEP_EVENTS} per drain ({@link stepTranscriptCap}),\n * so a large overflow never freezes one frame. Rendered events hold live\n * renderables/native buffers, so an unbounded buffer OOMs long\n * sessions (~0.13MB RSS per event measured in the TUI).\n *\n * Default {@link MAX_TRANSCRIPT_EVENTS}. Pass `null` to disable and\n * receive the full, unbounded event array, for example for\n * a host that renders a virtualized list or exports events verbatim.\n */\n maxTranscriptEvents?: number | null\n /**\n * Live override of {@link maxTranscriptEvents}, read inside each drain\n * commit. Use it when the cap changes between renders: a pending drain\n * updater runs during the host's next `useState` evaluation, BEFORE this\n * hook re-mirrors the option, so a render-mirrored value is one commit\n * stale (a history reveal would be re-trimmed by that commit).\n */\n getMaxTranscriptEvents?: () => number | null | undefined\n /**\n * What each event weighs on screen ({@link TranscriptWeight}), read at\n * each drain commit like {@link getMaxTranscriptEvents}: the cap then\n * counts rows, not events. Without it every event is a row.\n */\n getTranscriptWeight?: () => TranscriptWeight | undefined\n /**\n * Drain tick interval in ms, read each time the ticker starts. Match it to\n * the host's frame interval: ticking faster than frames paint only adds\n * commits nobody sees. The smooth cadence measures real elapsed time, so\n * the character rate does not depend on it. Default 16 (60 fps).\n */\n getTickIntervalMs?: () => number | undefined\n}\n\n/**\n * Take this tick's portion off every live bucket (mutating the buckets):\n * all of its thinking, and its markdown at the smooth cadence or in full.\n * `stillHasContent` = some bucket kept markdown for a later tick.\n */\nfunction takeTickPortions(\n buckets: Map<Owner, DeltaBucket>,\n smooth: boolean,\n dtMs: number,\n): { portions: DeltaBucket[], stillHasContent: boolean } {\n const portions: DeltaBucket[] = []\n let stillHasContent = false\n for (const bucket of buckets.values()) {\n const thinking = bucket.thinking\n bucket.thinking = ''\n\n let taken = ''\n if (bucket.markdown.length > 0) {\n if (smooth) {\n const { take, carry } = smoothCharsForTick(\n bucket.markdown.length,\n bucket.smoothCarry,\n dtMs,\n )\n bucket.smoothCarry = carry\n if (take > 0) {\n const result = takeChars(bucket.markdown, take)\n taken = result.taken\n bucket.markdown = result.rest\n if (bucket.markdown.length === 0)\n bucket.smoothCarry = 0\n }\n }\n else {\n taken = bucket.markdown\n bucket.markdown = ''\n bucket.smoothCarry = 0\n }\n }\n\n if (taken || thinking) {\n portions.push({\n markdown: taken,\n thinking,\n owner: bucket.owner,\n depth: bucket.depth,\n turnId: bucket.turnId,\n smoothCarry: 0,\n })\n }\n if (bucket.markdown.length > 0)\n stillHasContent = true\n }\n return { portions, stillHasContent }\n}\n\n/** No queued post-drain updater and no bucket holding content. */\nfunction bufferIdle(buckets: Map<Owner, DeltaBucket>, pendingUpdaters: number): boolean {\n let idle = pendingUpdaters === 0\n if (idle) {\n for (const bucket of buckets.values()) {\n if (bucket.markdown.length > 0 || bucket.thinking.length > 0) {\n idle = false\n break\n }\n }\n }\n return idle\n}\n\ninterface DrainWaiter { resolve: () => void, timer: ReturnType<typeof setTimeout> }\n\n/** Register a `whenDrained` waiter, with its no-progress safety timer armed. */\nfunction addDrainWaiter(\n resolve: () => void,\n drainProgressRef: MutableRefObject<number>,\n drainWaitersRef: MutableRefObject<DrainWaiter[]>,\n timeoutMs: number,\n): void {\n // Safety valve: a wedged ticker (cleared interval, starved event\n // loop) must not block the caller forever — resolve anyway and\n // drop the waiter so a late drain doesn't double-resolve. Armed\n // per window of NO progress: a tick that committed content since\n // the last check proves the drain is alive (a long response types\n // for longer than any fixed deadline), so re-arm instead of\n // snapping the tail into the caller's convergence.\n let seenProgress = drainProgressRef.current\n const waiter = {\n resolve,\n timer: undefined as unknown as ReturnType<typeof setTimeout>,\n }\n const arm = (): ReturnType<typeof setTimeout> => setTimeout(() => {\n if (drainProgressRef.current !== seenProgress) {\n seenProgress = drainProgressRef.current\n waiter.timer = arm()\n return\n }\n drainWaitersRef.current = drainWaitersRef.current.filter(w => w !== waiter)\n resolve()\n }, timeoutMs)\n waiter.timer = arm()\n drainWaitersRef.current.push(waiter)\n}\n\n/**\n * Buffers streaming deltas and commits them to the transcript on a ticker, in smooth or batched mode.\n *\n * @param setEvents - State setter for the transcript events.\n * @param options - Cadence and transcript cap settings.\n */\nexport function useStreamBuffer(\n setEvents: Dispatch<SetStateAction<StreamEvent[]>>,\n options?: StreamBufferOptions,\n): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null)\n // Real wall-clock timestamp of the last tick. Feeds the dt argument\n // to `smoothCharsForTick` so the drain rate stays accurate even when\n // `setInterval` is late (event-loop congestion, GC pauses); without\n // this a 50ms-late tick would only commit ~16ms-worth of chars and\n // the visible cadence would stall.\n const lastTickMsRef = useRef<number>(0)\n // Pin the latest `getSmooth` callback inside a ref so the ticker closure\n // always sees the current setting without recreating the timer.\n const getSmoothRef = useRef<(() => boolean) | undefined>(options?.getSmooth)\n getSmoothRef.current = options?.getSmooth\n /** Live mirror so drains read the current cap without re-memoizing. */\n const maxEventsRef = useRef<number | null | undefined>(options?.maxTranscriptEvents)\n maxEventsRef.current = options?.maxTranscriptEvents\n const getMaxEventsRef = useRef(options?.getMaxTranscriptEvents)\n getMaxEventsRef.current = options?.getMaxTranscriptEvents\n const getWeightRef = useRef(options?.getTranscriptWeight)\n getWeightRef.current = options?.getTranscriptWeight\n const getTickIntervalRef = useRef(options?.getTickIntervalMs)\n getTickIntervalRef.current = options?.getTickIntervalMs\n /** Interval of the running ticker (the nominal first-tick dt and the dt clamp). */\n const tickIntervalRef = useRef(TICK_INTERVAL_MS)\n const capForCommit = useCallback((events: StreamEvent[]): StreamEvent[] => {\n const cap = getMaxEventsRef.current ? getMaxEventsRef.current() : maxEventsRef.current\n // Stepped: a big overflow (a reveal handed back) trims over several drains.\n return cap === null ? events : stepTranscriptCap(events, cap ?? undefined, undefined, getWeightRef.current?.())\n }, [])\n\n /**\n * Updaters queued while smooth-streaming was still draining backlog —\n * tool-call appends, finalize-markdown transforms, error events.\n * Applied in FIFO order at the END of `tick()` once every bucket is\n * empty, so the trailing typewriter characters land BEFORE the\n * follow-up event instead of being clobbered by an immediate drain.\n *\n * The buffer treats markdown deltas as the authoritative ordering\n * source: a tool call queued behind 100 buffered chars surfaces after\n * those 100 chars have typed out, preserving the visual stream order\n * the user is reading.\n */\n const pendingUpdatersRef = useRef<((events: StreamEvent[]) => StreamEvent[])[]>([])\n\n /**\n * Pending `whenDrained` promises. Released (with their safety timers\n * cleared) whenever the buffer reaches the idle state: the tick that\n * empties the last bucket, a synchronous `drainNow`, or a `reset`.\n */\n const drainWaitersRef = useRef<DrainWaiter[]>([])\n\n /**\n * Monotonic count of commits (ticks that landed content or updaters).\n * `whenDrained` waiters compare it across their safety window: movement\n * means the ticker is alive — re-arm instead of firing the escape hatch.\n */\n const drainProgressRef = useRef(0)\n\n const releaseDrainWaiters = useCallback(() => {\n if (drainWaitersRef.current.length === 0)\n return\n const waiters = drainWaitersRef.current\n drainWaitersRef.current = []\n for (const waiter of waiters) {\n clearTimeout(waiter.timer)\n waiter.resolve()\n }\n }, [])\n\n const stopTicker = useCallback(() => {\n if (tickerRef.current) {\n clearInterval(tickerRef.current)\n tickerRef.current = null\n }\n // Forget the last-tick stamp so the next ticker run starts from\n // the nominal interval instead of a stale (potentially seconds-old)\n // delta that would burst-drain the first frame.\n lastTickMsRef.current = 0\n }, [])\n\n /**\n * Has at least one bucket got unflushed *markdown* content?\n *\n * Thinking content is excluded: it always flushes in full on every\n * tick (see `tick()`), so its presence in a bucket only matters until\n * the next 16 ms cycle — never long enough to justify deferring an\n * end-of-turn updater.\n */\n const hasMarkdownBacklog = useCallback((): boolean => {\n for (const bucket of bucketsRef.current.values()) {\n if (bucket.markdown.length > 0)\n return true\n }\n return false\n }, [])\n\n /**\n * Drain every bucket in full and synchronously run any queued\n * post-drain updaters plus the caller-provided one. Used by the\n * fast paths below: `flush`, and `appendImmediate` /\n * `flushAndUpdate` in batched mode or when no markdown backlog remains.\n */\n const drainNow = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n stopTicker()\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const queued = pendingUpdatersRef.current\n pendingUpdatersRef.current = []\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater && queued.length === 0) {\n releaseDrainWaiters()\n return\n }\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n for (const u of queued)\n merged = u(merged)\n if (updater)\n merged = updater(merged)\n // Memory bound: identity under the cap, turn-boundary head trim\n // above it (see capTranscriptEvents). Session turns stay complete.\n return capForCommit(merged)\n })\n releaseDrainWaiters()\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n /**\n * One tick of the continuous drain loop. Walks every live bucket and\n * commits a portion of its content based on the current mode. Once\n * every bucket empties out, fires any post-drain updaters that were\n * queued while smooth streaming was still in progress (turn\n * finalize, tool calls, errors) and stops the ticker.\n *\n * `thinking` content always flushes immediately even in smooth mode —\n * it's an internal-reasoning surface, surfaced for transparency rather\n * than for reading flow, and users expect it to keep up with the model.\n */\n const tick = useCallback(() => {\n const smooth = getSmoothRef.current?.() ?? true\n const buckets = bucketsRef.current\n\n // Real elapsed time since the last tick. Falls back to the nominal\n // interval on the first tick (when there's nothing to measure\n // against) and clamps to a sane upper bound so a one-off 500ms\n // event-loop stall doesn't burst-drain the entire backlog.\n const now = Date.now()\n const interval = tickIntervalRef.current\n const dtMs = lastTickMsRef.current === 0\n ? interval\n : Math.min(interval * 4, Math.max(1, now - lastTickMsRef.current))\n lastTickMsRef.current = now\n\n const { portions, stillHasContent } = takeTickPortions(buckets, smooth, dtMs)\n\n // Fold post-drain updaters into the same commit as the trailing\n // portions so the resulting transcript has at most one paint per\n // tick — and so the LAST portion of a smooth stream and its\n // companion finalize-markdown / tool-call event land atomically.\n const drainQueued = !stillHasContent && pendingUpdatersRef.current.length > 0\n const queued = drainQueued\n ? pendingUpdatersRef.current.splice(0, pendingUpdatersRef.current.length)\n : []\n\n if (portions.length > 0 || queued.length > 0) {\n drainProgressRef.current += 1\n setEvents((prev) => {\n let next = prev\n for (const portion of portions)\n next = applyBucket(next, portion)\n for (const u of queued)\n next = u(next)\n // Same memory bound as `drainNow`: deferred updaters append\n // tool-call / error events too, so this commit path must not\n // become a cap-bypass window. Identity under the cap.\n return capForCommit(next)\n })\n }\n\n if (!stillHasContent) {\n stopTicker()\n // Buckets and queued updaters both landed in this commit — the\n // buffer is idle, so end-of-stream convergence can proceed.\n releaseDrainWaiters()\n }\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n const ensureTicker = useCallback(() => {\n if (tickerRef.current)\n return\n const requested = getTickIntervalRef.current?.()\n tickIntervalRef.current = requested !== undefined && Number.isFinite(requested) && requested > 0 ? requested : TICK_INTERVAL_MS\n tickerRef.current = setInterval(tick, tickIntervalRef.current)\n }, [tick])\n\n /**\n * Decide between fast-drain and deferred-drain for an\n * updater-bearing call (`appendImmediate`, `flushAndUpdate`).\n *\n * Smooth mode + markdown backlog → ENQUEUE the updater behind the\n * trailing characters; the ticker picks it up once buckets empty.\n * Anything else → fall through to `drainNow` so the call retains\n * its synchronous semantics.\n */\n const drainOrDefer = useCallback((updater: (events: StreamEvent[]) => StreamEvent[]) => {\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n pendingUpdatersRef.current.push(updater)\n ensureTicker()\n return\n }\n drainNow(updater)\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flush = useCallback(() => {\n // In smooth mode with backlog, the ticker is already draining at\n // the typewriter cadence — explicit `flush()` shouldn't yank the\n // remaining characters into a single frame. The ticker is\n // self-stopping once empty, so this becomes a no-op there.\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n ensureTicker()\n return\n }\n drainNow()\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainOrDefer(update),\n [drainOrDefer],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainOrDefer(events => [...events, evt]),\n [drainOrDefer],\n )\n\n const queueStreamDelta = useCallback((\n kind: 'markdown' | 'thinking',\n delta: string,\n source?: StreamSource,\n ) => {\n if (!delta)\n return\n const owner: Owner = source?.childId ?? PARENT_OWNER\n const depth = source?.depth ?? 0\n let bucket = bucketsRef.current.get(owner)\n if (!bucket) {\n bucket = emptyBucket(owner, depth)\n bucketsRef.current.set(owner, bucket)\n }\n bucket[kind] += delta\n // Refresh on every delta — `turn:after` drains the bucket between turns,\n // so a turnId only ever applies to the deltas of one turn. Keeping the\n // latest also lets a turn that starts mid-bucket (rare but theoretically\n // possible if two hooks race the flush ticker) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n ensureTicker()\n }, [ensureTicker])\n\n const reset = useCallback(() => {\n stopTicker()\n bucketsRef.current.clear()\n pendingUpdatersRef.current = []\n // The dropped backlog will never drain — release waiters instead of\n // stranding them until the safety timeout.\n releaseDrainWaiters()\n }, [releaseDrainWaiters, stopTicker])\n\n const whenDrained = useCallback((timeoutMs: number = WHEN_DRAINED_TIMEOUT_MS): Promise<void> => {\n if (bufferIdle(bucketsRef.current, pendingUpdatersRef.current.length))\n return Promise.resolve()\n return new Promise(resolve => addDrainWaiter(resolve, drainProgressRef, drainWaitersRef, timeoutMs))\n }, [])\n\n // Unmount hygiene: the interval ticker outlives the render otherwise and\n // fires `setEvents` against an unmounted host. `reset` is stable (all\n // deps are useCallback'd), so this registers exactly once and never\n // perturbs the memoized return below.\n useEffect(() => reset, [reset])\n\n // CRITICAL: stabilize the returned object. All six callbacks are useCallback'd\n // (stable across renders), but a bare object-literal return would be a fresh\n // reference every call, which cascades through every `useCallback`/`useEffect`\n // that depends on `stream` and turns the resume effect into an unbounded\n // destroy/rebuild loop — the exact source of the terminal-host memory leak.\n return useMemo(\n () => ({ queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained],\n )\n}\n","import type { ReactNode } from 'react'\nimport type { SyntaxStyles, Theme, ThemeColors, ThemeSelect, ThemeSurfaces } from './theme'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// ThemeContext\n//\n// The TUI / GUI mounts `<ThemeProvider theme={…}>` near the root of its tree,\n// resolving the active theme from `Settings.theme`. Every component reads\n// theme values through a hook so a runtime theme switch (Settings → Theme)\n// triggers an immediate re-paint of the whole tree.\n//\n// Sub-hooks (`useColors`, `useSelectStyle`, …) exist so consumers can grab\n// just the slice they need, for example `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\n/**\n * Provides `theme` to the subtree.\n *\n * @param props - The `theme` to provide and the `children` to render.\n */\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\n/** Returns the current theme. Falls back to {@link DEFAULT_THEME} outside a {@link ThemeProvider}. */\nexport function useTheme(): Theme {\n return useContext(ThemeContext)\n}\n\n/** Color palette only — equivalent to `useTheme().colors`. */\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n\n/** Select-row styling — equivalent to `useTheme().select`: the `▶` marker and accent text carry the cursor. */\nexport function useSelectStyle(): ThemeSelect {\n return useContext(ThemeContext).select\n}\n\n/** Panel / surface backgrounds — equivalent to `useTheme().surfaces`. */\nexport function useSurfaces(): ThemeSurfaces {\n return useContext(ThemeContext).surfaces\n}\n\n/** Raw syntax style table — `useTheme().syntax`. Renderer converts to its native style type. */\nexport function useSyntaxStyles(): SyntaxStyles {\n return useContext(ThemeContext).syntax\n}\n","// ---------------------------------------------------------------------------\n// Transcript scroll-anchor computation.\n//\n// Walks a list of partitioned transcript items (one entry per row that\n// will be rendered) and assigns a stable scroll-anchor id to the FIRST\n// event of each turn — `turn-anchor-<turnId>`. The renderer applies\n// these ids to whichever container hosts the first event of the turn;\n// scroll-into-view (TUI scrollbox or GUI element.scrollIntoView) then\n// resolves the id to a known target.\n//\n// Pure data-in / data-out so the same logic powers the TUI today and\n// any future GUI without dragging OpenTUI specifics into the chat\n// layer.\n// ---------------------------------------------------------------------------\n\nimport type { StreamEvent } from './types'\n\n/**\n * One entry in the partitioned transcript-row list. Single events\n * render as a normal row; a `child-run` groups ONE subagent's events\n * from a consecutive run of subagent output into one bordered box\n * (concurrent siblings get a `child-run` each, so their rows never\n * braid together inside a shared frame); `tool-group`s\n * collect ALL parent-owned tool/tool-result events of one exchange\n * into a single collapsible summary row (`Settings.groupToolCalls`);\n * an `exchange-fold` goes further (`Settings.transcriptDensity:\n * 'compact'`) and takes every thinking, tool and worker event of the\n * exchange, leaving only the prompt and the assistant's prose in place.\n * `key` is stable across re-partitions of the same stream (derived\n * from the first grouped event) so expand state survives appends.\n *\n * Currently produced by the TUI's `partitionTranscript`; a GUI shell\n * grouping subagent runs the same way can produce the same shape.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'tool-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'exchange-fold', key: string, events: StreamEvent[], previous?: StreamEvent }\n /** Compact density: an exchange's earlier messages, ahead of its last one. */\n | { kind: 'message-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order\n * and, for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scroll target).\n * - `undefined` — later event of an already-tagged turn (or a\n * synthetic event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N\n * for subagent runs (one entry per inner event). `idByTurn` is the\n * inverse lookup used by the scroll effect. `lastTurnId` is the\n * most-recently-rendered turn — the scroll effect special-cases it to\n * snap to bottom rather than scroll the anchor into view (which would\n * stop short of the actual tail).\n *\n * Exported so the anchor-tagging matrix can be unit-tested without\n * rendering anything.\n *\n * @param items - Transcript items in render order.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transcript-anchors-DMjvyUjk.js","names":[],"sources":["../src/chat/boot-profiler.ts","../src/chat/completion.ts","../src/chat/completion-artifacts.ts","../src/chat/config-context.tsx","../src/chat/discovery-context.tsx","../src/chat/discovery-slot.ts","../src/chat/editor-command.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/hints.ts","../src/chat/interactions.tsx","../src/chat/mcp-auth-context.tsx","../src/chat/mcp-rows.ts","../src/chat/mcp-tool-toggle.ts","../src/chat/oauth-redirect.ts","../src/chat/running-calls.ts","../src/chat/safe-mode-context.tsx","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/transcript-anchors.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Boot-time wall-clock profiler.\n//\n// Sprinkle `bootTick('<label>')` at strategic points along the startup\n// chain (cli.ts → runTui → registerTreeSitterParsers → resolveConfig →\n// createCliRenderer → React mount → discovery effects → session\n// resume). When `ZIDANE_BOOT_PROFILE=1` is set, each call emits one\n// line on stderr with the delta since the previous tick and the total\n// since module load:\n//\n// $ ZIDANE_BOOT_PROFILE=1 zidane\n// [boot] +0.0ms / total 0.0ms / cli:enter\n// [boot] +12.3ms / total 12.3ms / cli:after-setupBundledTreeSitterAssets\n// [boot] +47.8ms / total 60.1ms / runTui:after-registerTreeSitterParsers\n// ...\n//\n// Renderer-agnostic on purpose — lives in `zidane/chat` so a future GUI\n// shell can wire it into its own boot path with the same env var and\n// the same output format. Writes to stderr, which OpenTUI's renderer\n// leaves alone (stdout is the frame buffer surface).\n// ---------------------------------------------------------------------------\n\nconst enabled = !!process.env.ZIDANE_BOOT_PROFILE\n/**\n * High-resolution origin for the running profile. Captured at module\n * load time so the first {@link bootTick} call inside the same process\n * reflects \"time since the boot profiler was first reached\", which for\n * the standard `cli.ts → runTui` path is effectively \"time since the\n * TUI binary started executing user code\".\n */\nconst start = performance.now()\nlet last = start\n\n/**\n * Record a checkpoint. No-op unless `ZIDANE_BOOT_PROFILE` is truthy in\n * the environment.\n *\n * The leading delta is the time since the PREVIOUS tick (so a long\n * delta highlights the immediately-preceding work); the trailing total\n * is the time since the profiler started. Both round to one decimal of\n * a millisecond.\n *\n * @param label - Name of the boot step that just finished.\n */\nexport function bootTick(label: string): void {\n if (!enabled)\n return\n const now = performance.now()\n const delta = now - last\n const total = now - start\n last = now\n // Delta and total are padded to 7 characters so the columns stay aligned\n // until a value widens past that. The label is not padded.\n process.stderr.write(\n `[boot] +${delta.toFixed(1).padStart(7)}ms / total ${total.toFixed(1).padStart(7)}ms / ${label}\\n`,\n )\n}\n\n/**\n * Returns `true` when `ZIDANE_BOOT_PROFILE` is set. Useful for guarding\n * heavier instrumentation (e.g. wrapping a costly call in a span) that\n * you don't want paying its own cost in the default path.\n */\nexport function bootProfileEnabled(): boolean {\n return enabled\n}\n","/**\n * Prompt autocompletion framework — React hook.\n *\n * The provider contract types + pure span/reference helpers live in\n * `./completion-core` (node-free + react-free, re-exported here). This\n * module adds the `useCompletion()` React hook on top.\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A GUI consumes the same hook to drive a dropdown. The popup component\n * itself only reads `label` + `description` on each item, so provider-\n * specific typing (`TItem`) stays at the registration boundary and never\n * leaks into the renderer.\n */\n\nimport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion-core'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { applyInsert, collectReferences, findActiveTriggers, mergeSuggestions } from './completion-core'\n\nexport type {\n ActiveTrigger,\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n CompletionSubmitContext,\n} from './completion-core'\nexport { applyInsert, collectReferences, findActiveTrigger, findActiveTriggers, mergeReferences, mergeSuggestions, resolveCompletionSubmit } from './completion-core'\n\n/**\n * Shared sentinel returned by `useCompletion` when no references are\n * present in the buffer. Stable identity lets consumers' `useEffect`\n * dep arrays bail out on chip-less keystrokes — see `useChipHighlights`.\n */\nconst EMPTY_REFS: readonly CompletionReference<unknown>[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// React hook\n// ---------------------------------------------------------------------------\n\n/** State surface returned by `useCompletion()`. */\nexport interface CompletionState<TItem = unknown> {\n /** Active trigger, or null when the cursor doesn't sit in a completable span. */\n active: ActiveTrigger<TItem> | null\n /** Items for the active query. Empty when no trigger is active. */\n items: readonly CompletionItem<TItem>[]\n /** True while the provider's `suggest` promise is pending. */\n loading: boolean\n /** Index of the highlighted item inside `items`. */\n selectedIndex: number\n /** Move the highlight. Wraps at the ends. No-op when `items` is empty. */\n selectNext: () => void\n /** Moves the highlight to the previous item. Wraps at the ends. No-op when `items` is empty. */\n selectPrev: () => void\n /**\n * Commit the highlighted item. Returns the new text + cursor pair, or\n * `null` when there's nothing to commit (no active trigger, empty list).\n */\n commit: () => { text: string, cursor: number } | null\n /** Force-close the popover until the user types again. */\n dismiss: () => void\n /** All references in the current buffer, regardless of provider. */\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Drive a prompt-completion popover from React state.\n *\n * Inputs are pull-style — the caller owns `text` + `cursor` (typically\n * mirroring an `OpenTUI TextareaRenderable` or a `<textarea>` element) and\n * passes them in each render. Providers are matched by their `trigger`\n * character; every provider that shares the trigger at the cursor is bound\n * and their suggestions merge into one list.\n *\n * Asynchronous suggest calls are aborted when the query changes or the\n * popover closes, so providers that hit a backend don't leak.\n *\n * @param input - Current buffer text and cursor offset.\n * @param providers - Providers that offer suggestions.\n * @param options - Options. `maxQueryLength` bounds the trigger query scan.\n */\nexport function useCompletion<TItem = unknown>(\n input: { text: string, cursor: number },\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): CompletionState<TItem> {\n const { text, cursor } = input\n // Destructure primitives up front so the `useMemo` dep array reads them\n // by value (not by `options` reference, which is a fresh object literal\n // on every call from most hosts).\n const { maxQueryLength } = options\n // `dismissed` is keyed by the trigger's `span.start` — the buffer\n // position of the `@` / `/` glyph that opened the popover. Staying\n // dismissed *while the user keeps typing into the same trigger span*\n // is the whole point of Esc; a stricter `${text.length}:${cursor}`\n // key would re-open on the very next keystroke, which would make the\n // \"esc dismiss\" hint a lie. The effect below resets this when the trigger\n // goes away entirely (user backspaced past `@`) so retyping `@` at\n // the same position works.\n const [dismissedTriggerStart, setDismissedTriggerStart] = useState<number | null>(null)\n\n const rawTrigger = useMemo(\n () => findActiveTriggers(text, cursor, providers, { maxQueryLength }),\n [text, cursor, providers, maxQueryLength],\n )\n\n const activeMulti = useMemo(() => {\n if (!rawTrigger)\n return null\n if (rawTrigger.span.start === dismissedTriggerStart)\n return null\n return rawTrigger\n }, [rawTrigger, dismissedTriggerStart])\n\n // `active` stays a single-provider ActiveTrigger (first bound provider) so\n // the popup's title/commit contract is unchanged; multi-provider merging\n // happens in the suggest effect below and surfaces as `item.section`.\n const active = useMemo<ActiveTrigger<TItem> | null>(\n () => activeMulti\n ? { provider: activeMulti.providers[0], query: activeMulti.query, span: activeMulti.span }\n : null,\n [activeMulti],\n )\n\n // Forget the dismissal once the trigger disappears — otherwise a\n // fresh `@` typed at the same byte position would inherit the prior\n // dismiss and the popover would never come back without scrolling\n // the trigger elsewhere first.\n useEffect(() => {\n if (!rawTrigger)\n setDismissedTriggerStart(null)\n }, [rawTrigger])\n\n const [items, setItems] = useState<readonly CompletionItem<TItem>[]>([])\n const [loading, setLoading] = useState(false)\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Cancel any in-flight suggest when the query changes / popover closes.\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => {\n abortRef.current?.abort()\n if (!activeMulti) {\n setItems([])\n setLoading(false)\n setSelectedIndex(0)\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n const ctx: CompletionContext = { text, cursor }\n let cancelled = false\n const outs = activeMulti.providers.map(provider => ({\n provider,\n out: provider.suggest(activeMulti.query, ctx, controller.signal),\n }))\n // Sync fast path — every provider returned synchronously (the common\n // single-provider case, e.g. `@` files). Merge without a loading flash.\n if (outs.every(o => Array.isArray(o.out))) {\n setItems(mergeSuggestions(outs.map(o => ({ provider: o.provider, items: o.out as CompletionItem<TItem>[] })), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n // allSettled, not all: with several providers on one trigger, a single\n // provider whose async suggest rejects (e.g. a failed catalog scan) must\n // not blank its siblings' results. Fulfilled groups merge; rejected ones\n // are dropped. (All rejecting → empty items.)\n void Promise.allSettled(outs.map(async o => ({ provider: o.provider, items: await o.out }))).then(\n (results) => {\n if (cancelled)\n return\n setItems(mergeSuggestions(results.flatMap(r => r.status === 'fulfilled' ? [r.value] : []), activeMulti.providers.length))\n setSelectedIndex(0)\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [activeMulti, text, cursor])\n\n // References don't depend on `cursor` — both built-in providers ignore\n // `ctx.cursor` in `parseReferences`, and the consumers (chip highlighting,\n // submit-time activation) only care about positions of references, not\n // about where the caret happens to sit. Dropping `cursor` from the dep\n // array avoids re-walking every provider regex on each arrow-key press.\n // `cursor` is still forwarded so a future provider that wants the cursor\n // can opt in — at the cost of choosing its own memoization strategy.\n //\n // Empty results collapse to a shared `EMPTY_REFS` constant so the prompt\n // textarea's `useChipHighlights` effect doesn't fire on every keystroke\n // of a chip-less buffer (each keystroke would otherwise allocate a fresh\n // `[]`, change identity, and trigger a `clearAllHighlights` repaint).\n const references = useMemo(\n () => {\n const refs = collectReferences(text, providers, cursor)\n return refs.length === 0 ? (EMPTY_REFS as readonly CompletionReference<TItem>[]) : refs\n },\n [text, providers],\n )\n\n const selectNext = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i + 1) % items.length))\n }, [items.length])\n const selectPrev = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i - 1 + items.length) % items.length))\n }, [items.length])\n const dismiss = useCallback(() => {\n // Pull from `rawTrigger`, not `active`, so dismiss-while-dismissed\n // (a no-op now but reasonable to support) keeps the right anchor.\n if (rawTrigger)\n setDismissedTriggerStart(rawTrigger.span.start)\n }, [rawTrigger])\n const commit = useCallback((): { text: string, cursor: number } | null => {\n if (!active || items.length === 0)\n return null\n const item = items[Math.min(selectedIndex, items.length - 1)]\n return applyInsert(text, active.span, item.insertText)\n }, [active, items, selectedIndex, text])\n\n // Memoize the returned bag so consumers placing it in a `useEffect`\n // dep array don't observe a fresh reference every render. Same pattern\n // (and rationale) as `useStreamBuffer.return` — see streaming.ts.\n return useMemo<CompletionState<TItem>>(\n () => ({ active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references }),\n [active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references],\n )\n}\n","/**\n * Completion provider for session artifacts. Trigger: `#` (shared with the\n * chats provider — the renderer groups both under one popup).\n *\n * `#pr` → \"acme/widgets#42\" (pull request, open) → inserts the PR URL. Unlike chats there\n * is no token grammar: the inserted text IS the locator (URL, `git:…`,\n * path), which the model can act on directly and a human can read. No\n * `parseReferences` — a pasted URL is not a reference to resolve.\n *\n * Catalog getter is host-owned and sync; the host keeps it fresh off the\n * `artifact:recorded` / `artifact:changed` signals the same way it keeps\n * the chats catalog. `ensureCatalog` covers the cold first `#` keystroke.\n */\n\nimport type { ExtensionArtifactRow } from '../extensions/types'\nimport type { SessionArtifact } from '../session/artifacts'\nimport type { CompletionItem, CompletionProvider } from './completion-core'\nimport { Fzf } from 'fzf'\nimport { ageString } from './format'\n\n/** Id of the artifacts completion provider. */\nexport const ARTIFACTS_COMPLETION_PROVIDER_ID = 'artifacts'\n\n/** An artifact that can be referenced from a prompt. */\nexport interface ArtifactReferenceEntry {\n /** The session artifact. */\n artifact: SessionArtifact\n /** Pre-rendered row (`renderArtifactRow`) — kind registry stays host-side. */\n row: ExtensionArtifactRow\n /** Human kind label for the description (`Pull request`). */\n kindLabel: string\n}\n\n/** Provider payload for an artifact reference. */\nexport interface ArtifactReferenceData {\n /** The referenced artifact. */\n entry: ArtifactReferenceEntry\n /** What gets inserted: `row.href` when present, else the raw `uri`. */\n locator: string\n}\n\nconst DEFAULT_RESULT_LIMIT = 30\n\n/** Kind → short query aliases so `#pr` and `#branch` hit without typing the label. */\nconst KIND_ALIASES: Record<string, readonly string[]> = {\n pull_request: ['pr', 'pull', 'mr'],\n branch: ['branch', 'br'],\n commit: ['commit', 'sha'],\n file: ['file', 'path'],\n}\n\nfunction locatorFor(entry: ArtifactReferenceEntry): string {\n return entry.row.href ?? entry.artifact.uri\n}\n\nfunction haystack(entry: ArtifactReferenceEntry): string {\n const aliases = KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]\n return [entry.row.title, entry.row.subtitle ?? '', entry.artifact.label, entry.kindLabel, ...aliases].join(' ')\n}\n\nfunction descriptionFor(entry: ArtifactReferenceEntry): string {\n const bits = [entry.kindLabel]\n if (entry.row.status)\n bits.push(entry.row.status)\n bits.push(ageString(entry.artifact.updatedAt))\n const loc = locatorFor(entry)\n if (loc !== entry.row.title)\n bits.push(loc)\n return bits.join(', ')\n}\n\n/**\n * Filters and ranks artifacts for a query.\n * An empty query returns every artifact. Otherwise it matches the kind aliases, kind label and a fuzzy text match.\n *\n * @param catalog - Artifacts to search.\n * @param query - Text typed after the trigger.\n * @param limit - Maximum number of items returned.\n */\nexport function scoreArtifacts(\n catalog: readonly ArtifactReferenceEntry[],\n query: string,\n limit = DEFAULT_RESULT_LIMIT,\n): CompletionItem<ArtifactReferenceData>[] {\n const q = query.trim().toLowerCase()\n const fuzzy = q.length > 0\n ? new Set(new Fzf(catalog, { selector: haystack, casing: 'case-insensitive' }).find(q).map(r => r.item))\n : null\n const scored: { entry: ArtifactReferenceEntry, rank: number }[] = []\n for (const entry of catalog) {\n if (q.length === 0) {\n scored.push({ entry, rank: 5 })\n continue\n }\n const title = entry.row.title.toLowerCase()\n const aliases = (KIND_ALIASES[entry.artifact.kind] ?? [entry.artifact.kind]).map(a => a.toLowerCase())\n const kindLabel = entry.kindLabel.toLowerCase()\n if (aliases.some(a => a === q || a.startsWith(q)) || kindLabel.startsWith(q)) {\n scored.push({ entry, rank: 0 })\n continue\n }\n if (title.startsWith(q)) {\n scored.push({ entry, rank: 1 })\n continue\n }\n if (title.includes(q) || locatorFor(entry).toLowerCase().includes(q)) {\n scored.push({ entry, rank: 2 })\n continue\n }\n if (fuzzy?.has(entry))\n scored.push({ entry, rank: 4 })\n }\n scored.sort((a, b) => a.rank - b.rank || b.entry.artifact.updatedAt - a.entry.artifact.updatedAt)\n return scored.slice(0, limit).map<CompletionItem<ArtifactReferenceData>>(({ entry }) => {\n const locator = locatorFor(entry)\n return {\n id: `artifact:${entry.artifact.id}`,\n label: entry.row.title,\n description: descriptionFor(entry),\n insertText: `${locator} `,\n data: { entry, locator },\n }\n })\n}\n\n/**\n * Creates a completion provider for `#` artifact references.\n * The provider only suggests. It inserts the artifact's locator as plain text, so it parses no references back.\n *\n * @param opts - Catalog getter, an optional async catalog loader and a result limit (default 30).\n */\nexport function createArtifactsCompletionProvider(opts: {\n getCatalog: () => readonly ArtifactReferenceEntry[]\n ensureCatalog?: () => Promise<readonly ArtifactReferenceEntry[]>\n limit?: number\n}): CompletionProvider<ArtifactReferenceData> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n return {\n id: ARTIFACTS_COMPLETION_PROVIDER_ID,\n trigger: '#',\n label: 'Artifacts',\n suggest(query) {\n const current = opts.getCatalog()\n if (current.length > 0 || !opts.ensureCatalog)\n return scoreArtifacts(current, query, limit)\n return opts.ensureCatalog().then(loaded => scoreArtifacts(loaded, query, limit))\n },\n // Inserted text is a plain locator, not a token: nothing to parse back.\n parseReferences: () => [],\n }\n}\n","import type { ReactNode } from 'react'\nimport type { ResolvedConfig } from './config'\nimport { createContext, useContext } from 'react'\n\n// ---------------------------------------------------------------------------\n// ConfigContext — renderer-agnostic React context that hands a fully-resolved\n// chat config down to deep consumers (modals, pickers, screens). Kept in its\n// own file so callers that only want `resolveConfig()` don't pay the\n// `import 'react'` cost.\n// ---------------------------------------------------------------------------\n\n/** Exported for hooks that read the config when there is one (`useModelName`). */\nexport const ConfigContext = createContext<ResolvedConfig | null>(null)\n\n/**\n * Provides a {@link ResolvedConfig} to the components below it.\n *\n * @param props - Props with the resolved `config` and the `children` to render.\n */\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\n/** Reads the {@link ResolvedConfig} from the nearest {@link ConfigProvider}. Throws when used outside one. */\nexport function useConfig(): ResolvedConfig {\n const ctx = useContext(ConfigContext)\n if (!ctx)\n throw new Error('useConfig must be used inside <ConfigProvider>')\n return ctx\n}\n","/**\n * Live catalogs for files / skills / MCPs surfaced through React\n * context.\n *\n * **Why context, not props.** The modal layer stores its open node in\n * `useState`. React's element-identity reconciliation skips re-rendering\n * a subtree when the element reference hasn't changed — so passing\n * `skillsCatalog` as a prop into `modal.open(<SettingsModal …>)` bakes\n * the snapshot at open time and any subsequent `setSkillsCatalog`\n * doesn't reach the modal. Context propagation IS forwarded through\n * the modal layer regardless of element identity (same path the MCP\n * auth state uses), so reading via `useDiscovery()` gives the modal\n * live state.\n *\n * **Shape.** The provider exposes three groups:\n *\n * - **Catalogs** — the current state. Consumers re-render on every\n * update.\n * - **Ensure thunks** — kick the first load (idempotent). Wired into\n * completion providers; also called by the settings modal on mount\n * to fire-load lazy catalogs (currently `files`) on demand.\n * - **Refresh thunks** — force a rescan, bypassing the SWR throttle.\n * Wired into the settings modal's `ctrl+R`.\n *\n * Renderer-agnostic by design: the context only carries data + thunks.\n * The discovery work itself lives in `*-discovery.ts` modules; the\n * stale-while-revalidate machinery lives in `discovery-slot.ts`. The\n * provider's caller (AppShell for the TUI) owns where the slots live\n * and how their state propagates to `<DiscoveryProvider value>`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpToolSchema } from '../mcp'\nimport type { SkillConfig } from '../skills'\nimport type { FileEntry } from './files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from './mcps-discovery'\nimport type { DiscoveredPlugin } from './plugins-discovery'\nimport { createContext, useContext } from 'react'\n\n/** Skills, plugins and MCP servers found on disk, shared with the UI. */\nexport interface DiscoveryContextValue {\n /** Parsed `SkillConfig[]` from project + user scan paths. */\n skillsCatalog: readonly SkillConfig[]\n /**\n * Agent Plugins loaded from `.agents/plugins` / `.{prefix}/plugins`\n * (project + user). Includes rejected plugins — they render as read-only\n * diagnostics rather than vanishing.\n */\n pluginsCatalog: readonly DiscoveredPlugin[]\n /** Parsed `mcps.json` entries. */\n mcpsCatalog: readonly DiscoveredMcp[]\n /** Non-fatal parse errors from `mcps.json` lookups. */\n mcpsErrors: readonly DiscoveryError[]\n /**\n * Per-server full tool catalog from the on-disk cache (see\n * `chat/mcp-tools-cache.ts`). Updated as MCP servers bootstrap and\n * fire the `mcp:tools:list` hook. Missing key → no cache yet; the\n * settings UI surfaces an empty-state row under the expanded server.\n */\n mcpToolsByServer: Record<string, readonly McpToolSchema[]>\n /** Current file walk results — empty until lazy-load lands. */\n filesCatalog: readonly FileEntry[]\n\n /** Force a fresh skills rescan. Resolves once state is committed. */\n refreshSkills: () => Promise<void>\n /** Force a fresh plugin rescan. Resolves once state is committed. */\n refreshPlugins: () => Promise<void>\n /**\n * Re-parse `mcps.json`. Sync-shaped under the hood, async for symmetry.\n * Resolves `true` when the catalog actually changed (a publish that\n * schedules downstream agent rebuilds), `false` on a fingerprint no-op\n * or scan failure — callers that arm rebuild-coupled state (e.g. the\n * add-MCP auto-login) disarm on `false`.\n */\n refreshMcps: () => Promise<boolean>\n /** Force a fresh file walk. No UI affordance calls it yet. */\n refreshFiles: () => Promise<void>\n\n /** Idempotent first-load trigger for skills (used by `/` popover + modal mount). */\n ensureSkills: () => Promise<readonly SkillConfig[]>\n /** Idempotent first-load trigger for files (used by `@` popover). */\n ensureFiles: () => Promise<readonly FileEntry[]>\n}\n\nconst DiscoveryContext = createContext<DiscoveryContextValue | null>(null)\n\n/**\n * Provides a {@link DiscoveryContextValue} to the components below it.\n *\n * @param props - Props with the discovery `value` and the `children` to render.\n */\nexport function DiscoveryProvider({\n value,\n children,\n}: {\n value: DiscoveryContextValue\n children: ReactNode\n}) {\n return (\n <DiscoveryContext.Provider value={value}>\n {children}\n </DiscoveryContext.Provider>\n )\n}\n\n/**\n * Read live discovery state + actions. Throws if used outside a\n * `<DiscoveryProvider>` — discovery is a load-bearing dependency for\n * settings + completion popovers; bailing loud here surfaces wiring\n * mistakes at mount instead of producing empty catalogs at first\n * keystroke.\n */\nexport function useDiscovery(): DiscoveryContextValue {\n const ctx = useContext(DiscoveryContext)\n if (!ctx)\n throw new Error('useDiscovery must be used inside <DiscoveryProvider>')\n return ctx\n}\n\n/**\n * Non-throwing variant — returns `null` when no provider is mounted.\n * Used by composable modals (`<SettingsModal>`, …) that accept catalog\n * props as a fallback for embedders who don't wire the full TUI\n * shell. The in-app flow always mounts `<DiscoveryProvider>` so the\n * modal sees live state; standalone embeds get the prop snapshot.\n */\nexport function useDiscoveryOptional(): DiscoveryContextValue | null {\n return useContext(DiscoveryContext)\n}\n","/**\n * Stale-while-revalidate catalog slot.\n *\n * One slot per (catalog kind, projectDir) pin. Holds the first-load\n * promise + a `lastScannedAt` timestamp + an in-flight `refreshing`\n * promise. Idempotent across concurrent callers, safe to rotate when\n * the project changes (the slot's `abort` controller cancels in-flight\n * walks and the `.then` guards drop stale results).\n *\n * Renderer-agnostic. AppShell wraps it in React refs so React closures\n * stay stable; a non-TUI shell can use it directly against its own\n * state propagation.\n *\n * Contract:\n * - `ensure()` — kick the first load if not started; return the\n * cached promise. On subsequent calls, if `Date.now() -\n * lastScannedAt >= throttleMs` and no refresh is in flight, kick\n * a background revalidate that calls `onLoad(fresh)` when it\n * resolves. The original first-load promise still resolves with\n * the first walk's contents — consumers reading live state\n * elsewhere (React state) see the refreshed catalog.\n * - `refresh()` — force a rescan now, bypassing the throttle.\n * De-duplicates: if a refresh is already in flight, returns the\n * same promise.\n * - `isRefreshing()` — whether a refresh is running now.\n * - `abort()` — cancel any in-flight walk and detach. After this\n * the slot is dead; callers should rotate to a new slot.\n *\n * No state is exposed except through promises + the host's `onLoad`\n * callback; the slot doesn't try to be a store, only a coordinator.\n */\n\n/** Walk function — takes an abort signal, returns the discovered list. */\nexport type Walk<T> = (signal: AbortSignal) => Promise<readonly T[]>\n\n/** Notification: a fresh walk has resolved. Host is expected to commit to its own state store. */\nexport type Commit<T> = (items: readonly T[]) => void\n\n/** Options for {@link createDiscoverySlot}. */\nexport interface DiscoverySlotOptions<T> {\n /** Discover function — runs the actual FS / network walk. */\n walk: Walk<T>\n /** Commit hook — receives every successful walk's result (first + refreshes). */\n onLoad: Commit<T>\n /**\n * Min interval between background refreshes triggered by `ensure()`.\n * Default: 3_000ms. `refresh()` ignores this — it's the user's\n * explicit-intent path.\n */\n throttleMs?: number\n /** Optional error sink. Defaults to no-op; useful for `debugLog`. */\n onError?: (err: unknown, phase: 'first-load' | 'refresh') => void\n}\n\n/** Handle returned by {@link createDiscoverySlot}. */\nexport interface DiscoverySlot<T> {\n /**\n * Kick the first load (idempotent). Returns the first-load promise\n * once it exists. Schedules a background refresh if past throttle.\n */\n ensure: () => Promise<readonly T[]>\n /** Force-refresh now. De-duped across concurrent callers. */\n refresh: () => Promise<void>\n /** True if a refresh is currently running. */\n isRefreshing: () => boolean\n /** Cancel in-flight work + detach. Idempotent. */\n abort: () => void\n}\n\n/**\n * Creates a slot that loads a list lazily and keeps it fresh.\n * `ensure()` starts the first walk once and schedules throttled background refreshes. `refresh()` forces a walk. Every successful walk result goes to `onLoad`.\n *\n * @param options - The walk function, commit hook and optional throttle and error sink.\n */\nexport function createDiscoverySlot<T>(options: DiscoverySlotOptions<T>): DiscoverySlot<T> {\n const throttleMs = options.throttleMs ?? 3_000\n const abortCtrl = new AbortController()\n let firstLoad: Promise<readonly T[]> | null = null\n let refreshing: Promise<void> | null = null\n let lastScannedAt = 0\n let aborted = false\n\n const handleError = (err: unknown, phase: 'first-load' | 'refresh'): void => {\n options.onError?.(err, phase)\n }\n\n const startRefresh = (): Promise<void> => {\n lastScannedAt = Date.now()\n const run = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return\n options.onLoad(items)\n })\n .catch((err) => {\n if (aborted)\n return\n handleError(err, 'refresh')\n })\n .finally(() => {\n if (refreshing === run)\n refreshing = null\n })\n refreshing = run\n return run\n }\n\n const slot: DiscoverySlot<T> = {\n ensure() {\n if (aborted)\n return Promise.resolve([] as readonly T[])\n if (!firstLoad) {\n lastScannedAt = Date.now()\n firstLoad = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return [] as readonly T[]\n options.onLoad(items)\n return items\n })\n .catch((err) => {\n if (!aborted)\n handleError(err, 'first-load')\n return [] as readonly T[]\n })\n return firstLoad\n }\n // SWR: revalidate in the background if stale and not already running.\n if (!refreshing && Date.now() - lastScannedAt >= throttleMs)\n void startRefresh()\n return firstLoad\n },\n refresh() {\n if (aborted)\n return Promise.resolve()\n if (refreshing)\n return refreshing\n // Forced refresh from a user-initiated path (e.g. settings\n // modal `ctrl+R`). If the first load hasn't happened yet,\n // bootstrap through `ensure` so subsequent ensure callers see\n // the right `firstLoad` promise.\n if (!firstLoad)\n return slot.ensure().then(() => {})\n return startRefresh()\n },\n isRefreshing() {\n return refreshing !== null\n },\n abort() {\n if (aborted)\n return\n aborted = true\n abortCtrl.abort()\n },\n }\n return slot\n}\n","/**\n * `$EDITOR` / `$VISUAL` command parsing.\n *\n * These variables conventionally hold a command LINE, not a bare executable —\n * `code --wait`, `cursor --wait`, `subl -w`, `emacsclient -nw` are all normal\n * values. Spawning the whole string as argv[0] looks for a file literally named\n * `cursor --wait` and fails with ENOENT, so the flags have to be split off.\n *\n * Kept out of the TUI (and free of `node:child_process`) so the\n * tokenizer is unit-testable without pulling in the TUI.\n */\n\n/**\n * Split an editor command line into `[executable, ...args]`.\n *\n * Whitespace-separated, with single and double quotes honored so an editor\n * living under a path with spaces still works\n * (`\"/Applications/My App/bin/ed\" -w`). Quotes group and are stripped;\n * backslash escaping is deliberately NOT implemented, because on Windows\n * backslash is the path separator and treating `C:\\tools\\ed` as escapes would\n * corrupt far more command lines than it would rescue.\n *\n * Returns `[]` for an empty or whitespace-only spec, so callers can fall back\n * to the platform opener rather than spawning `''`.\n *\n * @param spec - Raw editor command line, such as the value of `$EDITOR`.\n * @example\n * ```ts\n * import { parseEditorCommand } from 'zidane/chat'\n *\n * const [cmd, ...args] = parseEditorCommand('code --wait')\n * // cmd === 'code', args === ['--wait']\n * ```\n */\nexport function parseEditorCommand(spec: string): string[] {\n const tokens: string[] = []\n let current = ''\n // The quote character we're inside, or null at top level. Tracked (rather\n // than a boolean) so a double quote inside single quotes stays literal.\n let quote: '\"' | '\\'' | null = null\n // Distinct from `current !== ''` so `\"\"` yields an empty-string token instead\n // of vanishing — an explicitly quoted empty argument was still an argument.\n let quoted = false\n\n for (const ch of spec) {\n if (quote) {\n if (ch === quote)\n quote = null\n else\n current += ch\n continue\n }\n if (ch === '\"' || ch === '\\'') {\n quote = ch\n quoted = true\n continue\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n if (current !== '' || quoted) {\n tokens.push(current)\n current = ''\n quoted = false\n }\n continue\n }\n current += ch\n }\n if (current !== '' || quoted)\n tokens.push(current)\n return tokens\n}\n\n/**\n * Re-quote a single argv token for a shell that will re-parse the joined command\n * line — i.e. `spawn(..., { shell: true })`, which Node uses on Windows so that\n * `.cmd` / `.bat` wrappers (how `code`, `cursor` and friends ship there) resolve.\n *\n * Node does NOT re-quote when `shell` is set: it joins `[cmd, ...args]` with\n * spaces and hands the string over. Since {@link parseEditorCommand} has already\n * STRIPPED the user's quotes, handing tokens back unquoted would split\n * `C:\\Program Files\\ed.exe` at the space and try to run `C:\\Program`. This puts\n * the quotes back.\n *\n * Only tokens that need it are quoted, so a plain `code` stays `code`. A literal\n * double quote is doubled, which is how `cmd.exe` escapes one inside a quoted\n * run.\n *\n * The test covers `cmd.exe` METACHARACTERS, not just whitespace: `&`, `|`, `^`,\n * `<`, `>` and friends carry none of the obvious \"needs quoting\" signals, so a\n * repo cloned into `C:\\work\\a&b` would otherwise hand `C:\\work\\a&b\\AGENTS.md`\n * over bare, and cmd would split it at the `&` and run the tail as a second\n * command.\n *\n * `%` and `!` are in the class so such a token is quoted rather than passed\n * bare — but be clear about the limit: quoting does NOT stop cmd.exe expanding\n * `%VAR%`, nor `!VAR!` where delayed expansion is on. Double quotes suppress\n * splitting and other syntax, not expansion. That is a property of the shell\n * which no amount of quoting can close; the worst case is a wrong path, not an\n * injected command.\n *\n * @param token - One argv token, already stripped of its original quotes.\n */\nexport function quoteForShell(token: string): string {\n if (token === '')\n return '\"\"'\n if (!/[\\s\"&|^<>%()!]/.test(token))\n return token\n return `\"${token.replace(/\"/g, '\"\"')}\"`\n}\n","import type { ReactNode } from 'react'\nimport type { BooleanSettingKey } from './settings-catalog'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { enforceSettingsInvariants } from './settings-catalog'\n\n// The React-free catalog lives in `settings-catalog.ts`.\nexport * from './settings-catalog'\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the settings.\n//\n// `onChange` is called with the latest settings whenever a row toggles, so\n// the host can persist them. Persistence pathing lives outside this module\n// to keep the settings layer portable across renderers (TUI, GUI, …).\n// ---------------------------------------------------------------------------\n\nexport interface SettingsContextValue {\n settings: Settings\n /** Flip a boolean setting in place. Restricted at the type level to boolean-valued keys. */\n toggle: (key: BooleanSettingKey) => void\n /** Write any setting key to a new value. Used by string-valued choice rows. */\n setSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void\n /** One line the Settings modal shows (a server refused the last write); cleared by the next change. */\n status?: string | null\n /**\n * Settings sync markers, while the modal edits a server another computer\n * syncs to (`docs/SERVER.md` § Settings sync): which setting came from\n * which device, which was edited on the server since (an override), and\n * dropping an override. Absent elsewhere.\n */\n sync?: SettingsSyncMarks\n}\n\n/** A setting's sync state on the server the modal edits. */\nexport interface SettingsSyncMarks {\n marker: (key: string) => { kind: 'synced' | 'override', device: string } | null\n /** Drop the override on `key`; false when it has none. */\n reset: (key: string) => boolean\n}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\n/**\n * Holds the settings state, applies {@link enforceSettingsInvariants} on every write and reports each change to `onChange`.\n *\n * @param props - `initial` settings, an optional `onChange` callback and the `children` to render.\n */\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n // Normalized on load too: an older build (or a hand edit) may have persisted\n // a dependent toggle without its requirement.\n const [settings, setSettings] = useState<Settings>(() => enforceSettingsInvariants(initial))\n\n // `onChange` runs as a side-effect *after* commit, not inside the state\n // updater. Two reasons:\n // 1. React 18 strict-mode dev runs invoke state updaters twice for\n // every transition; a `disk.save(...)` inside the updater would\n // double-fire there.\n // 2. Pure-update is the React contract — updaters must be free of\n // observable side-effects. Persisting from inside breaks the\n // contract regardless of strict mode.\n //\n // A ref keeps the latest `onChange` reachable from the effect without\n // forcing the effect to re-run on every render the parent re-renders.\n // The `initialMount` ref skips the very first commit (we don't want to\n // persist the initial state we just loaded from disk).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const initialMount = useRef(true)\n useEffect(() => {\n if (initialMount.current) {\n initialMount.current = false\n return\n }\n onChangeRef.current?.(settings)\n }, [settings])\n\n const toggle = useCallback((key: BooleanSettingKey) => {\n setSettings(prev => enforceSettingsInvariants({ ...prev, [key]: !prev[key] }))\n }, [])\n\n const setSetting = useCallback(<K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings(prev => (prev[key] === value ? prev : enforceSettingsInvariants({ ...prev, [key]: value })))\n }, [])\n\n const value = useMemo<SettingsContextValue>(\n () => ({ settings, toggle, setSetting }),\n [settings, toggle, setSetting],\n )\n\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/**\n * Re-provides settings to a subtree with other values and writers: a\n * surface that edits somewhere else than this window (the TUI's Settings\n * modal while it edits a remote host) reuses every settings-reading\n * component unchanged.\n */\nexport function SettingsOverride({ value, children }: { value: SettingsContextValue, children: ReactNode }) {\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\n/** Returns the settings context value. Throws when used outside {@link SettingsProvider}. */\nexport function useSettings(): SettingsContextValue {\n const ctx = useContext(SettingsContext)\n if (!ctx)\n throw new Error('useSettings must be used inside <SettingsProvider>')\n return ctx\n}\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills`, `settings.enabledMcps`,\n * `settings.enabledExtensions` and `settings.enabledPlugins` (see\n * {@link EnabledAllowlistKey}).\n *\n * Semantics, kept identical across every call site:\n * - `undefined` → every catalog entry is enabled (default — user has\n * never opened the picker; new entries flow in).\n * - `[]` → the whole subsystem is off.\n * - `[names]` → explicit allowlist; the persisted shape stays a\n * concrete array once the user has toggled anything.\n *\n * The hook exposes a live `enabledSet` (Set<string>) + a `toggle(name)`\n * callback that persists the result through `useSettings().setSetting`.\n * The first toggle seeds the persisted allowlist from the current catalog\n * so newly-added entries don't silently drop on the next launch.\n *\n * Renderer-neutral: works for the TUI's `<ToggleListModal>`, a GUI\n * checkbox list, a CLI flag tool. Hook tests live alongside the chat\n * suite; the rendering layer is tested where it lives.\n */\n\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Settings keys whose value type is `readonly string[] | undefined`. */\nexport type EnabledAllowlistKey = 'enabledSkills' | 'enabledMcps' | 'enabledExtensions' | 'enabledPlugins'\n\n/**\n * Reactive handle over a persisted enabled-names allowlist, returned by {@link useEnabledToggleSet}.\n */\nexport interface EnabledToggleSet {\n /** Live set of enabled names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Toggle one name on/off; persists immediately via `setSetting`. */\n toggle: (name: string) => void\n}\n\n/**\n * Bind an \"enabled allowlist\" setting to a discovered catalog.\n *\n * `keyOf` extracts the persisted identity from a catalog entry (skill name,\n * MCP server name, …). Pass a stable, collision-free key — the persisted\n * allowlist is keyed against it forever.\n *\n * @param opts - Catalog, key extractor, setting key, and optional extra keys to keep when pruning the allowlist.\n */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /**\n * Names that survive the persisted-allowlist GC even when absent from\n * `catalog`. Use when the catalog deliberately hides entries that are\n * still known to exist — e.g. errored / untrusted extensions are kept\n * out of the toggle list but must not be dropped from the allowlist\n * by an unrelated toggle. Defaults to the catalog keys.\n */\n gcKeys?: readonly string[]\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey, gcKeys } = opts\n const persisted = settings[settingKey]\n\n const enabledSet = useMemo<ReadonlySet<string>>(() => {\n if (persisted === undefined)\n return new Set(catalog.map(keyOf))\n return new Set(persisted)\n }, [persisted, catalog, keyOf])\n\n // Live catalog membership — used to garbage-collect stale persisted\n // names on every write. Without this, a skill / MCP that was once\n // enabled and then removed from disk lingers in the JSON forever\n // (the next toggle round-trips it back into the array). Functionally\n // harmless today (the agent ignores unknown names) but it lets the\n // persisted state drift away from reality. `gcKeys` widens the\n // \"known\" set beyond the rendered catalog (see its JSDoc).\n const catalogKeys = useMemo(\n () => new Set(gcKeys ?? catalog.map(keyOf)),\n [gcKeys, catalog, keyOf],\n )\n\n const toggle = useCallback((name: string) => {\n // Seed from the current `enabledSet` (covers both the seeded-from-catalog\n // and explicit-allowlist branches). Persist as a sorted array filtered\n // against the live catalog so stale names — entries deleted from disk\n // between launches — don't linger forever in the persisted allowlist.\n // `undefined` is reserved for \"fresh install, accept new discoveries\";\n // once the user toggles once the persistence shape stays a concrete list.\n const next = new Set(enabledSet)\n if (next.has(name))\n next.delete(name)\n else\n next.add(name)\n const persisted = [...next]\n .filter(n => catalogKeys.has(n))\n .sort()\n setSetting(settingKey, persisted)\n }, [enabledSet, catalogKeys, settingKey, setSetting])\n\n return { enabledSet, toggle }\n}\n","// ---------------------------------------------------------------------------\n// Footer / title hint primitives.\n//\n// Pure data + width-estimation helpers consumed by the TUI's bottom-bar\n// footer, the prompt-box overlay, the sessions header, and any future\n// GUI shell that wants to render the same hint rows: `<key> <label>`, two\n// blank columns between hints. Plain-text math only — no renderer primitives, no colour\n// resolution, no React. Width estimates are intentionally `.length`-\n// based (assumes ASCII / single-cell glyphs); the consumer's renderer\n// is responsible for the actual painting and any wide-glyph accounting\n// it needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Single hint entry — `<key> <label>`, optionally followed by an inline\n * secondary chord (`<key><extra.key> <label> <extra.label>`) for compound\n * shortcuts like `ctrl+m/l model effort`.\n *\n * Colour fields are advisory hex strings (resolved against the active\n * theme by the renderer); leaving them undefined lets the renderer pick\n * its conventional fallback (e.g. `warn` for keys, `dim` for labels; the\n * secondary chord takes its key's color).\n */\nexport interface Hint {\n /** Key or chord shown for the hint, such as `ctrl+l`. */\n key: string\n /** Short action text shown after the key. */\n label: string\n /** Optional override for the key color. Renderer default: warn. */\n keyColor?: string\n /** Optional override for the label color. Renderer default: dim. */\n labelColor?: string\n /**\n * Optional secondary chord + value rendered inline after the primary\n * pair: `<key><extra.key> <label> <extra.label>`. Lets one hint advertise\n * a compound shortcut (e.g. `ctrl+m/l model effort`) without forcing\n * callers into a parallel struct or a custom renderer.\n */\n extra?: {\n key: string\n label: string\n /**\n * Dispatchable binding spec behind `extra.key` when the displayed\n * chord is an abbreviation (`/l` for `ctrl+l` — see `shortChord`).\n * The abbreviation itself doesn't parse as a keypress, so a renderer\n * that makes the secondary chord independently clickable needs the\n * full spec. Defaults to `extra.key` when omitted.\n */\n keySpec?: string\n /** Defaults to the primary key's color on the renderer side: `ctrl+m/l` reads as one binding. */\n keyColor?: string\n /** Defaults to dim on the renderer side. */\n labelColor?: string\n }\n}\n\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * @param text - Text to shorten.\n * @param max - Maximum length in characters, ellipsis included.\n */\nexport function truncateTrailing(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max === 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/** Blank columns between two hints in a row. */\nexport const HINT_GAP = 2\n\n/**\n * Column layout of one hint, from its first cell:\n *\n * `ctrl+m/l` ` gpt-5` ` high fast`\n * └ keys ──┘ └label─┘ └extra label┘\n *\n * `key` then `extra.key`, a space, the label; an empty key with no extra\n * drops the space (a label-only hint). The TUI's click zones and the width\n * math below both read this, so they can't drift apart.\n */\nexport interface HintLayout {\n /** Columns `[extraKeyStart, extraKeyEnd)` hold `extra.key` (empty range without one). */\n extraKeyStart: number\n extraKeyEnd: number\n /** First column of the space before `extra.label`; equals `width` without an extra. */\n extraLabelStart: number\n /** Total columns. */\n width: number\n}\n\n/**\n * Layout of a single hint, see {@link HintLayout}. Pure / total.\n *\n * @param h - Hint to measure.\n */\nexport function hintLayout(h: Hint): HintLayout {\n const extraKeyStart = h.key.length\n const extraKeyEnd = extraKeyStart + (h.extra?.key.length ?? 0)\n const labelEnd = extraKeyEnd + (extraKeyEnd > 0 && h.label !== '' ? 1 : 0) + h.label.length\n return {\n extraKeyStart,\n extraKeyEnd,\n extraLabelStart: labelEnd,\n width: labelEnd + (h.extra ? 1 + h.extra.label.length : 0),\n }\n}\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered as a\n * hint row (keys, labels, {@link HINT_GAP} between hints). Exported so\n * prompt-box overlays can run the same responsive math as the bottom-bar\n * footer when deciding whether trigger hints fit. Pure / total.\n *\n * @param hints - Hints to measure.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n return hints.reduce((sum, h, i) => sum + hintLayout(h).width + (i > 0 ? HINT_GAP : 0), 0)\n}\n\n/**\n * Stable empty list so callers can compare by reference. Exported so\n * any consumer that needs to feed \"no primary hints\" into the same\n * `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's\n * minimal UI mode, where the prompt overlay shows only `@` / `/`\n * triggers) reuses the same frozen array instead of allocating a new\n * one each render.\n */\nexport const EMPTY_HINTS: readonly Hint[] = Object.freeze([])\n\n/**\n * Return the longest prefix of `hints` whose rendered width fits within\n * `budget`. Used to degrade hint rows gracefully at narrow terminal\n * widths instead of letting an absolutely-positioned hint row wrap\n * mid-segment (which paints the overflow over surrounding borders and\n * looks like garbled glyphs in a TUI).\n *\n * Prefix-only (no reordering, no last-hint priority) so the survivors\n * keep their authored order — the user's muscle memory for \"leftmost\n * hint = primary action\" stays intact as the terminal shrinks.\n *\n * @param hints - Hints in display order.\n * @param budget - Available width in characters.\n */\nexport function clipHintsToWidth(hints: readonly Hint[], budget: number): readonly Hint[] {\n if (budget <= 0 || hints.length === 0)\n return EMPTY_HINTS\n const out: Hint[] = []\n let used = 0\n for (let i = 0; i < hints.length; i++) {\n const h = hints[i]!\n const cost = (i > 0 ? HINT_GAP : 0) + hintLayout(h).width\n if (used + cost > budget)\n break\n out.push(h)\n used += cost\n }\n if (out.length === 0)\n return EMPTY_HINTS\n return out.length === hints.length ? hints : out\n}\n","/**\n * The React side of interactions: a FIFO queue of pending requests with\n * stable actions, shared by the TUI and GUI. The protocol (tools, requests,\n * serialization, pending-state derivation) lives in `interactions-protocol.ts`\n * and is re-exported here.\n */\nimport type { ReactNode } from 'react'\nimport type { InteractionResponse, InteractionsActions, PendingInteractionEntry } from './interactions-protocol'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\nexport * from './interactions-protocol'\n\n// ---------------------------------------------------------------------------\n// React context — FIFO queue of pending requests + resolver callbacks.\n//\n// Two contexts on purpose (mirrors `safe-mode-context.tsx`):\n// - `InteractionsQueueContext` carries the live queue. Consumers re-render\n// on every push/pop.\n// - `InteractionsActionsContext` carries `{ enqueue, resolveHead, … }` and\n// never changes identity, so callbacks that depend on these don't\n// re-bind on every queue churn.\n// ---------------------------------------------------------------------------\n\nconst InteractionsQueueContext = createContext<readonly PendingInteractionEntry[]>([])\nconst InteractionsActionsContext = createContext<InteractionsActions | null>(null)\n\n/**\n * React provider that holds the FIFO queue of pending interactions. Pending entries are cancelled when it unmounts.\n *\n * @param props - Component props.\n * @param props.children - Subtree that can read the queue and actions.\n */\nexport function InteractionsProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly PendingInteractionEntry[]>([])\n\n // Mirror the live queue so the unmount cleanup can settle whatever is\n // pending without re-subscribing the effect on every queue change.\n const queueRef = useRef<readonly PendingInteractionEntry[]>([])\n queueRef.current = queue\n\n // Provider unmount is a teardown path too: React drops the queue state\n // outright, so any in-flight `requestInteraction` Promise would hang\n // forever (its tool body never settles → the run's `running` gate never\n // clears). Cancel every pending entry on unmount so the resolver can't be\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const entry of queueRef.current)\n entry.cancel('Interactions provider unmounted')\n }, [])\n\n const enqueue = useCallback((entry: PendingInteractionEntry) => {\n setQueue(prev => [...prev, entry])\n }, [])\n\n const resolveHead = useCallback((response: InteractionResponse) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(response)\n return rest\n })\n }, [])\n\n const cancelHead = useCallback((reason?: string) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.cancel(reason)\n return rest\n })\n }, [])\n\n const cancelAll = useCallback((reason?: string) => {\n setQueue((prev) => {\n for (const entry of prev) entry.cancel(reason)\n return []\n })\n }, [])\n\n const dropById = useCallback((requestId: string) => {\n // Remove WITHOUT settling: the daemon already resolved this in the\n // window that answered it. Neither resolve nor cancel — both would\n // reach back toward the (no-op) daemon callback needlessly.\n setQueue(prev => prev.filter(entry => entry.request.id !== requestId))\n }, [])\n\n // Stable actions identity — every member is `useCallback`'d with `[]`\n // deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useInteractionsActions()`\n // never re-render purely because of queue state.\n const actionsRef = useRef<InteractionsActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { enqueue, resolveHead, cancelHead, cancelAll, dropById }\n\n return (\n <InteractionsActionsContext.Provider value={actionsRef.current}>\n <InteractionsQueueContext.Provider value={queue}>\n {children}\n </InteractionsQueueContext.Provider>\n </InteractionsActionsContext.Provider>\n )\n}\n\n/** Read the live queue (re-renders on every push/pop). */\nexport function useInteractionsQueue(): readonly PendingInteractionEntry[] {\n return useContext(InteractionsQueueContext)\n}\n\n/** Read the stable actions object. */\nexport function useInteractionsActions(): InteractionsActions {\n const ctx = useContext(InteractionsActionsContext)\n if (!ctx)\n throw new Error('useInteractionsActions must be used inside <InteractionsProvider>')\n return ctx\n}\n","/**\n * React provider for the MCP per-server OAuth state map.\n *\n * `ModalRoot.open(node)` captures the modal contents as a snapshot — naive\n * prop-passing would freeze the auth badge at whatever status existed when\n * the user opened the picker, and a `mcp:auth:url` arriving mid-login\n * would never reach the rendered modal. Routing the state through a\n * context provider mounted ABOVE `ModalRoot` fixes that: a state update\n * re-renders the provider, which re-renders `ModalRoot`, which re-renders\n * the (referentially-stable) modal child node — and consumers calling\n * `useMcpAuthState()` see the fresh value.\n *\n * Two contexts on purpose:\n *\n * - `StateContext` carries the live map. Consumers re-render on every\n * auth event.\n * - `DispatchContext` carries the reducer entry point. Identity is stable\n * across renders, so AppShell's hook handlers don't re-bind when the\n * state changes — they grab `dispatch` once and fire events for the\n * lifetime of the agent.\n *\n * Actions (login / logout / cancel) deliberately do NOT live in context —\n * they depend on AppShell-scoped resources (credential store, catalog ref,\n * agent ref), close over those via refs, and stay stable across renders.\n * Passing them as modal props at `modal.open` time is fine because the\n * captured reference reads from refs internally; the snapshot doesn't go\n * stale even though it was taken at open time.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpAuthEvent, McpAuthStateMap } from './mcp-auth-state'\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { reduceMcpAuth } from './mcp-auth-state'\n\nconst StateContext = createContext<McpAuthStateMap>({})\nconst DispatchContext = createContext<((event: McpAuthEvent) => void) | null>(null)\n\n/**\n * Mount once near the top of the React tree (above `ModalRoot`). Children\n * use `useMcpAuthState()` to read and `useMcpAuthDispatch()` to fire events.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function McpAuthProvider({ children }: { children: ReactNode }) {\n const [state, setState] = useState<McpAuthStateMap>({})\n const dispatch = useCallback((event: McpAuthEvent) => {\n setState(prev => reduceMcpAuth(prev, event))\n }, [])\n return (\n <StateContext.Provider value={state}>\n <DispatchContext.Provider value={dispatch}>\n {children}\n </DispatchContext.Provider>\n </StateContext.Provider>\n )\n}\n\n/** Returns the current auth state map, keyed by server name. */\nexport function useMcpAuthState(): McpAuthStateMap {\n return useContext(StateContext)\n}\n\n/** Returns the function that applies an {@link McpAuthEvent} to the auth state. Throws when used outside {@link McpAuthProvider}. */\nexport function useMcpAuthDispatch(): (event: McpAuthEvent) => void {\n const dispatch = useContext(DispatchContext)\n if (!dispatch)\n throw new Error('useMcpAuthDispatch must be used inside <McpAuthProvider>')\n return dispatch\n}\n","/**\n * Renderer-agnostic data model for the MCP settings list when it\n * supports per-server expand / collapse with nested tool rows.\n *\n * Lives in `chat/` (not `tui/`) because both the standalone\n * `McpsSettingsModal` and the inline `<McpsList>` inside the unified\n * `SettingsModal` consume the exact same flattening + cursor\n * arithmetic — re-deriving it in two places would let the two\n * surfaces drift on edge cases (empty tool catalogs, freshly-removed\n * servers, stale expansion state).\n *\n * The model is a flat `VisibleMcpRow[]` so renderers can iterate once,\n * and the cursor is a single integer that walks both kinds (server\n * headers AND tool checkboxes) without separate \"which server am I\n * on / which tool inside it\" state.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { DiscoveredMcp } from './mcps-discovery'\n\n/**\n * One row in the flattened MCP settings list. Three kinds:\n *\n * - `'server'` — server header with the chevron + checkbox +\n * transport detail. Always present.\n * - `'tool'` — checkbox + description for a tool advertised\n * by the parent server. Present only when the\n * server is expanded AND its tool cache is\n * populated.\n * - `'tool-empty'` — placeholder shown when an expanded server\n * has no cached tool list yet (first launch\n * before any session has bootstrapped the\n * server). Lets the renderer surface a\n * one-line \"send a prompt to populate\" hint\n * WITHOUT changing the server's expand\n * affordance.\n */\nexport type VisibleMcpRow\n = | { kind: 'server', entry: DiscoveredMcp }\n | { kind: 'tool', serverName: string, tool: McpToolSchema }\n | { kind: 'tool-empty', serverName: string }\n\n/**\n * Flatten a server catalog plus the per-server expanded set into the\n * visible-row list a renderer cycles through.\n *\n * - `catalog` — filtered / ordered server list (post-search,\n * post-allow-list).\n * - `expanded` — names of servers whose tool list is open.\n * Servers not in this set render as a single\n * server-header row.\n * - `toolsByServer` — per-server full tool catalog (from\n * `chat/mcp-tools-cache.ts`). Missing key →\n * `'tool-empty'` placeholder under the\n * expanded server.\n *\n * @param catalog - Discovered servers in display order.\n * @param expanded - Names of servers whose tool rows are shown.\n * @param toolsByServer - Cached tool lists keyed by server name. A missing or empty list yields a `tool-empty` row.\n */\nexport function buildVisibleMcpRows(\n catalog: readonly DiscoveredMcp[],\n expanded: ReadonlySet<string>,\n toolsByServer: Record<string, readonly McpToolSchema[]>,\n): VisibleMcpRow[] {\n const out: VisibleMcpRow[] = []\n for (const entry of catalog) {\n out.push({ kind: 'server', entry })\n if (!expanded.has(entry.config.name))\n continue\n const tools = toolsByServer[entry.config.name] ?? []\n if (tools.length === 0) {\n out.push({ kind: 'tool-empty', serverName: entry.config.name })\n continue\n }\n for (const tool of tools)\n out.push({ kind: 'tool', serverName: entry.config.name, tool })\n }\n return out\n}\n\n/**\n * Server name a row \"belongs to\" for catalog / toggle / auth lookups.\n * For a server row that's its own name; for tool / tool-empty rows\n * it's the parent server's name.\n *\n * @param row - Any visible row.\n */\nexport function parentServerName(row: VisibleMcpRow): string {\n return row.kind === 'server' ? row.entry.config.name : row.serverName\n}\n\n/**\n * Index in `rows` of the server header for the given parent name. `-1`\n * when not present. Used by collapse handlers to snap the cursor up\n * to the parent BEFORE shrinking the row list — otherwise the cursor\n * would land on whichever sibling row now occupies the same flat\n * index.\n *\n * @param rows - The visible rows.\n * @param parentName - Server name to look for.\n */\nexport function indexOfServerRow(rows: readonly VisibleMcpRow[], parentName: string): number {\n return rows.findIndex(\n r => r.kind === 'server' && r.entry.config.name === parentName,\n )\n}\n","/**\n * Renderer-agnostic per-tool toggle for MCP servers. Sibling of\n * {@link useEnabledToggleSet} — same shape, opposite polarity:\n *\n * - `useEnabledToggleSet` is an **allowlist** (`enabledMcps` / etc.),\n * `undefined` = everything enabled, toggle moves to an explicit list.\n * - `useMcpToolToggleSet` is a **deny-list** (`disabledMcpTools`),\n * missing entry = everything enabled, toggle moves to an explicit\n * list of NAMES TO HIDE.\n *\n * The polarity flip is intentional and documented on the setting itself\n * (`Settings.disabledMcpTools`): when an MCP server ships a new tool in\n * a later release, a user who has already opted out of `big_tool` still\n * gets the new one — opposite preference from server-level toggles\n * where a new server should NOT silently appear in the catalog.\n *\n * Two hook flavors:\n *\n * - {@link useMcpToolToggleSet} — one server. Convenience for hosts\n * that render a single server's tool list at a time.\n * - {@link useMcpToolToggleMap} — every server's toggle handler in\n * one call. Required by hosts that dispatch toggles from a shared\n * keyboard handler at the top of the tree (`mcps-settings.tsx`)\n * where calling the single-server hook in a loop would violate\n * rules-of-hooks the moment a server is added or removed.\n *\n * Both flavors route through the same {@link buildToolToggle} factory\n * so semantics stay byte-identical regardless of entry point.\n *\n * Garbage-collects on every write: stale names (tools that disappeared\n * from the server's catalog between launches) are dropped so the\n * persisted deny-list doesn't grow unbounded. Same invariant as the\n * sibling hook for `enabledX` allowlists.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { Settings } from './types'\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Per-tool enable state and toggle handler for one MCP server. */\nexport interface McpToolToggleSet {\n /** Live set of ENABLED tool names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Flip one tool on/off; persists immediately via `setSetting`. */\n toggle: (toolName: string) => void\n}\n\n/**\n * Bind a single MCP server's per-tool toggle to\n * `Settings.disabledMcpTools[serverName]`.\n *\n * `catalog` is the FULL list of tools the server advertises (i.e. the\n * cached `listTools()` payload from `chat/mcp-tools-cache.ts`). The\n * hook needs it both to render `enabledSet` (every catalog tool not in\n * the persisted deny-list) and to garbage-collect stale deny-list\n * entries on every write.\n *\n * @param opts - The server name and its full tool catalog.\n */\nexport function useMcpToolToggleSet(opts: {\n serverName: string\n catalog: readonly McpToolSchema[]\n}): McpToolToggleSet {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n\n const catalogNames = useMemo(() => opts.catalog.map(t => t.name), [opts.catalog])\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(\n () => buildToolToggle({\n serverName: opts.serverName,\n catalogNames,\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n }),\n [opts.serverName, catalogNames, persistedDenyAll, setDisabled],\n )\n}\n\n/**\n * Bind every server in `catalogByServer` to its own per-tool toggle in\n * a single hook call. Returns a `serverName → McpToolToggleSet` map.\n *\n * Use this when a host needs handlers for multiple servers at once\n * (the TUI's MCP settings panel keyboard dispatcher is the canonical\n * example) — calling {@link useMcpToolToggleSet} in a `for` / `.map`\n * over a dynamic catalog would change the hook-call order whenever a\n * server is added or removed, violating React's rules-of-hooks. This\n * hook calls `useMemo` exactly once regardless of server count.\n *\n * Servers absent from `catalogByServer` get no entry in the result.\n * The persisted deny-list still keeps their entries on disk — they\n * just stay invisible to this consumer until the catalog re-includes\n * them.\n *\n * @param opts - Tool catalogs keyed by server name.\n */\nexport function useMcpToolToggleMap(opts: {\n catalogByServer: Record<string, readonly McpToolSchema[]>\n}): Record<string, McpToolToggleSet> {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(() => {\n const out: Record<string, McpToolToggleSet> = {}\n for (const [serverName, catalog] of Object.entries(opts.catalogByServer)) {\n out[serverName] = buildToolToggle({\n serverName,\n catalogNames: catalog.map(t => t.name),\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n })\n }\n return out\n }, [opts.catalogByServer, persistedDenyAll, setDisabled])\n}\n\n/**\n * Pure factory shared by both hook flavors. Captures `persistedDenyAll`\n * + `setDisabledMcpTools` at the call site so toggle handlers stay\n * referentially stable across renders that don't touch the underlying\n * settings entry — important for `useKeyboard` to avoid re-binding on\n * every catalog change.\n *\n * Exported for direct use by non-React consumers (CLI, GUI shells)\n * that want the same write semantics without the hook plumbing.\n *\n * @param args - Server name, the server's catalog tool names, the persisted deny-list for all servers and the setter that writes it.\n */\nexport function buildToolToggle(args: {\n serverName: string\n catalogNames: readonly string[]\n persistedDenyAll: Settings['disabledMcpTools']\n setDisabledMcpTools: (next: Settings['disabledMcpTools']) => void\n}): McpToolToggleSet {\n const { serverName, catalogNames, persistedDenyAll, setDisabledMcpTools } = args\n const persistedDeny = persistedDenyAll?.[serverName]\n const catalogSet = new Set(catalogNames)\n\n const enabledSet: ReadonlySet<string> = (() => {\n if (!persistedDeny || persistedDeny.length === 0)\n return new Set(catalogNames)\n const deny = new Set(persistedDeny)\n return new Set(catalogNames.filter(name => !deny.has(name)))\n })()\n\n const toggle = (toolName: string): void => {\n // Compute the next deny-list against the persisted state (NOT\n // `enabledSet`) so we never accidentally promote \"implicit-enabled-\n // because-no-entry\" tools into an explicit list on the first\n // toggle. GC stale names at the same time so the persisted file\n // tracks reality.\n const currentDeny = new Set(persistedDeny ?? [])\n if (currentDeny.has(toolName))\n currentDeny.delete(toolName)\n else\n currentDeny.add(toolName)\n\n const nextForServer = [...currentDeny]\n .filter(n => catalogSet.has(n))\n .sort()\n\n const prevMap = persistedDenyAll ?? {}\n const nextMap: Record<string, readonly string[]> = { ...prevMap }\n if (nextForServer.length === 0) {\n // Empty entry collapses back to \"no customization\" so the\n // persisted file stays tidy when a user re-enables every tool\n // they previously disabled.\n delete nextMap[serverName]\n }\n else {\n nextMap[serverName] = nextForServer\n }\n\n // Top-level `undefined` represents \"no per-tool prefs anywhere\".\n // Restore that when the last server entry empties out so the\n // diff against defaults stays minimal.\n const next: Settings['disabledMcpTools']\n = Object.keys(nextMap).length === 0 ? undefined : nextMap\n setDisabledMcpTools(next)\n }\n\n return { enabledSet, toggle }\n}\n","/**\n * Manual OAuth redirect-URL handoff.\n *\n * Users who run zidane over SSH (or behind a proxy / firewall that blocks\n * loopback) can't have the local browser hit the in-process callback server.\n * They CAN, however, copy the URL their browser was redirected to —\n * `http://127.0.0.1:<port>/callback?code=...&state=...` — and paste it back\n * into the TUI.\n *\n * The trick: that URL IS the callback our local server is listening on. We\n * just `fetch()` it ourselves. The server runs in the same process; the\n * request hits its handler exactly as if a real browser had arrived, the\n * OAuth promise (`waitForCode` for pi-ai providers, `startOAuthCallback` for\n * MCP) resolves through the normal happy path, and the upstream flow\n * continues uninterrupted. No new code path inside the OAuth state machine.\n *\n * Defense in depth: we reject anything that isn't a loopback URL — fetching\n * an arbitrary user-pasted URL from inside the agent process would be a\n * trivial SSRF.\n */\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])\n\n/**\n * The `redirect_uri` the authorization server was told to come back to,\n * read out of the authorization URL itself. That's our loopback callback\n * (`http://127.0.0.1:<port>/callback`) — the UI shows it so an SSH user\n * knows which address to fish out of the browser's URL bar, and\n * {@link normalizeRedirectPaste} uses it to rebuild a full callback URL\n * from a partial paste.\n *\n * Returns `null` when the URL doesn't parse or carries no usable\n * `redirect_uri` — callers degrade to \"paste the whole URL\".\n *\n * @param authUrl - The authorization URL to read.\n */\nexport function redirectUriFromAuthUrl(authUrl: string): URL | null {\n let parsed: URL\n try {\n parsed = new URL(authUrl)\n }\n catch {\n return null\n }\n const redirect = parsed.searchParams.get('redirect_uri')\n if (!redirect)\n return null\n try {\n return new URL(redirect)\n }\n catch {\n return null\n }\n}\n\n/**\n * Widen what counts as an acceptable paste. The happy path is the full\n * redirect URL out of the browser's address bar, but users on SSH\n * routinely end up holding less than that: some authorization servers\n * render the code on the page instead of redirecting, and copying from a\n * \"can't reach this site\" error page loses everything but the query.\n *\n * Accepted forms, given the authorization URL for context:\n *\n * - `http://127.0.0.1:5123/callback?code=x&state=y` — passthrough (any\n * absolute URL is returned untouched; the loopback guard in\n * {@link fetchOAuthRedirect} still has final say, so a pasted\n * `https://evil.test` is rejected there, not silently rewritten).\n * - `/callback?code=x&state=y` — path + query onto the callback origin.\n * - `?code=x&state=y` / `code=x&state=y` — query onto the callback URL.\n * - `x` — a bare authorization code; `state` is taken from the\n * authorization URL when it carried one (the loopback server forwards\n * `state` verbatim and the SDK owns verification, so a missing one is\n * not fatal). Anything with whitespace inside is prose (an error page\n * fragment), not a code — refused with a clearer error than a server\n * 400 would give.\n *\n * Throws when the input can't be turned into a URL — no `authUrl` context\n * for a partial paste, or an unusable authorization URL.\n *\n * @param pasted - What the user pasted: a full URL, a path, a query string or a bare code.\n * @param authUrl - The authorization URL on screen. Required to complete anything shorter than a full URL.\n */\nexport function normalizeRedirectPaste(pasted: string, authUrl?: string): string {\n const trimmed = pasted.trim()\n if (!trimmed)\n throw new Error('Paste the redirect URL from your browser.')\n // Absolute URL → caller's problem to validate. Checked by scheme rather\n // than `new URL()` success so a bare code like `abc` (which URL happily\n // rejects) and a scheme-ful `mcp://…` are told apart deterministically.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed))\n return trimmed\n\n const callback = authUrl ? redirectUriFromAuthUrl(authUrl) : null\n if (!callback) {\n throw new Error(\n 'That doesn\\'t look like a URL. Paste the full address your browser tried to reach (starting with http://127.0.0.1).',\n )\n }\n\n // `/callback?code=…` — keep the pasted path, it may differ from the one\n // we registered (a server that normalized a trailing slash, say). Same\n // outcome guard as the query branch below: a bare `/callback` carries no\n // OAuth result, and fetching it would only produce an opaque server 400\n // where a targeted message tells the user exactly what to re-copy.\n if (trimmed.startsWith('/')) {\n const merged = new URL(trimmed, callback)\n if (!merged.searchParams.has('code') && !merged.searchParams.has('error'))\n throw new Error('That path has no \"code\" parameter. Paste the full address from your browser\\'s URL bar.')\n return merged.toString()\n }\n\n const target = new URL(callback.toString())\n const query = trimmed.startsWith('?') ? trimmed.slice(1) : trimmed\n // A bare authorization code is a single URL-legal token — including\n // base64 padding, so trailing `=` stays code-like (`abc==`). Any OTHER\n // placement of `=` means key/value pairs (`foo=bar`, `state=…&code=…`):\n // treat as a query string, never as a code — URL-encoding `foo=bar` into\n // `code=foo%3Dbar` would fire a doomed request where a targeted error\n // tells the user what to re-copy.\n const codeLike = /^[^=\\s]+=*$/.test(query)\n if (!codeLike && query.includes('=')) {\n const params = new URLSearchParams(query)\n if (!params.has('code') && !params.has('error'))\n throw new Error('That fragment has no \"code\" parameter. Paste the full address from your browser, or just the code.')\n for (const [key, value] of params)\n target.searchParams.set(key, value)\n return target.toString()\n }\n\n // Bare code. Whitespace-bearing pastes never reach here (`codeLike`\n // excludes them) except as pure prose without `=`, so keep the explicit\n // reject for those: sending `code=access%20denied` to the callback\n // server would 400 without explaining anything.\n if (/\\s/.test(query))\n throw new Error('That doesn\\'t look like an authorization code. Paste the full address from your browser, or just the code itself.')\n target.searchParams.set('code', query)\n const state = new URL(authUrl!).searchParams.get('state')\n if (state)\n target.searchParams.set('state', state)\n return target.toString()\n}\n\n/** Outcome of calling the loopback callback server. */\nexport interface FetchOAuthRedirectResult {\n /** Status code from the callback server. 2xx = success. */\n status: number\n /** Best-effort message extracted from the response body (HTML title or trimmed text). */\n message?: string\n}\n\n/**\n * Treat `pasted` as a callback-URL paste. Validates it's a loopback URL,\n * fires a GET, returns the status.\n *\n * Pass `authUrl` (the authorization URL currently on screen) to accept\n * partial pastes — a bare code, a query string, a path — via\n * {@link normalizeRedirectPaste}. Without it only a full absolute URL is\n * accepted.\n *\n * Throws when:\n * - `pasted` doesn't parse as a URL (and can't be completed from `authUrl`).\n * - The URL host isn't loopback (rejects SSRF).\n * - The fetch errors out (network, timeout).\n *\n * Returns success/failure for non-2xx responses; the caller decides whether\n * a 4xx is fatal (state mismatch is a 400 the user can retry from).\n *\n * @param pasted - What the user pasted: a callback URL, or a partial one when `options.authUrl` is set.\n * @param options - Abort signal, request timeout in milliseconds (`5000` by default) and the authorization URL used to complete partial pastes.\n */\nexport async function fetchOAuthRedirect(\n pasted: string,\n options: { signal?: AbortSignal, timeoutMs?: number, authUrl?: string } = {},\n): Promise<FetchOAuthRedirectResult> {\n const trimmed = normalizeRedirectPaste(pasted, options.authUrl)\n\n let url: URL\n try {\n url = new URL(trimmed)\n }\n catch {\n throw new Error('That doesn\\'t look like a URL. Paste the full address from your browser.')\n }\n\n // Strip brackets that some browsers add around IPv6 hosts so the\n // comparison hits LOOPBACK_HOSTS regardless of textual form.\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n `Expected a loopback URL (127.0.0.1 / localhost), got \"${url.hostname}\". The browser should have redirected to a localhost address.`,\n )\n }\n\n // Short timeout: the callback server is in-process, so any meaningful\n // delay means it's gone (closed by a prior abort) or never started.\n const timeoutMs = options.timeoutMs ?? 5000\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n options.signal?.addEventListener('abort', () => ac.abort(), { once: true })\n\n let response: Response\n try {\n response = await fetch(url.toString(), { signal: ac.signal })\n }\n catch (err) {\n if ((err as Error).name === 'AbortError')\n throw new Error('No response from the local callback server. Was the login cancelled?')\n throw new Error(`Could not reach the local callback server: ${(err as Error).message}`)\n }\n finally {\n clearTimeout(timer)\n }\n\n const bodyText = await response.text().catch(() => '')\n return { status: response.status, message: extractMessage(bodyText) }\n}\n\n/**\n * Pick the most useful one-line message out of the callback server's HTML\n * response. pi-ai uses `<h1>` for the heading; the MCP callback uses\n * a leading `<p>`. We try both, then fall back to a stripped snippet.\n */\nfunction extractMessage(html: string): string | undefined {\n if (!html)\n return undefined\n const h1 = /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i.exec(html)?.[1]\n if (h1)\n return stripTags(h1).trim() || undefined\n const p = /<p[^>]*>([\\s\\S]*?)<\\/p>/i.exec(html)?.[1]\n if (p)\n return stripTags(p).trim() || undefined\n return undefined\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#39;/g, '\\'')\n}\n","/**\n * The calls still running in a transcript read from disk. The assistant turn\n * holding a step's calls is saved before they run, their results (with\n * `durationMs`) once all of them end, so a page read mid-call shows a call\n * with neither a result nor a start. The host's running work\n * (`chat.listWork`, with `children` for the subagents' calls) has the\n * start: stamped back on, the call's row counts from when it began, not\n * from when the page was read.\n *\n * Shared by the TUI's attach and the desktop app's transcript pages.\n */\nimport type { RunningWorkItem } from './host/signals'\nimport type { StreamEvent } from './types'\n\n/** What these read off an event: a `StreamEvent`, or a client's copy of one. */\ntype CallEvent = Pick<StreamEvent, 'kind' | 'callId' | 'childId' | 'startedAt' | 'resuming'>\n\n/** A call by its agent and id: ids are only unique within one agent. */\nfunction callKey(callId: string, childId: string | undefined): string {\n return `${childId ?? ''}\\u0000${callId}`\n}\n\n/**\n * Whether a call, a subagent's included, has neither a result nor a start: a\n * call that may still be running, untimed. A page without one needs no\n * running work.\n *\n * @param events - The page's events.\n */\nexport function hasUntimedOpenCalls(events: readonly CallEvent[]): boolean {\n const done = new Set(events.flatMap(e => (e.kind === 'tool-result' && e.callId ? [callKey(e.callId, e.childId)] : [])))\n return events.some(e => e.kind === 'tool' && e.callId !== undefined && e.startedAt === undefined && !done.has(callKey(e.callId, e.childId)))\n}\n\n/**\n * `events` with each running call's start from the host's running work, a\n * subagent's call matched by its `childId`, and its `resuming` mark (a\n * call of a batch a durable run resumed after a process death). `events`\n * itself when nothing is stamped.\n *\n * @param events - The page's events.\n * @param work - The session's running work (`chat.listWork`).\n */\nexport function withRunningCallStarts<E extends CallEvent>(events: E[], work: readonly Pick<RunningWorkItem, 'kind' | 'id' | 'startedAt' | 'childId' | 'resuming'>[]): E[] {\n const running = new Map(work.flatMap(item => (item.kind === 'tool' ? [[callKey(item.id, item.childId), item] as const] : [])))\n if (running.size === 0)\n return events\n let stamped = false\n const out = events.map((event) => {\n const item = event.kind === 'tool' && event.callId ? running.get(callKey(event.callId, event.childId)) : undefined\n const startedAt = item && event.startedAt === undefined ? item.startedAt : undefined\n const resuming = item?.resuming === true && event.resuming !== true\n if (startedAt === undefined && !resuming)\n return event\n stamped = true\n return { ...event, ...(startedAt !== undefined ? { startedAt } : {}), ...(resuming ? { resuming: true as const } : {}) }\n })\n return stamped ? out : events\n}\n","/**\n * Safe-mode React context — bridges the agent's `tool:gate` / `mcp:tool:gate`\n * hooks (which run inside the harness loop) with the TUI's React tree (which\n * renders the approval picker).\n *\n * Flow:\n * 1. Hook handler in `tui/host/host-session-callbacks.ts` calls `requestApproval(tool, input)`.\n * 2. Context appends a pending entry to the queue, returns a Promise.\n * 3. Picker renders the queue's head, calls `resolve(decision)` on pick.\n * 4. Head is shifted off the queue; hook handler proceeds with the decision.\n *\n * The queue is FIFO so parallel tool calls prompt in arrival order.\n *\n * Two contexts on purpose:\n * - `SafeModeQueueContext` carries the live queue. Consumers re-render on\n * every push/pop.\n * - `SafeModeActionsContext` carries `{ requestApproval, resolveHead,\n * denyAll, dropByCorrelation }` and never changes identity, so callbacks that depend on\n * these don't re-bind every time a tool call enters or leaves the queue.\n */\n\nimport type { ReactNode } from 'react'\nimport type { ModelApproval } from './types'\nimport { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'\n\n/**\n * Outcome of an approval prompt. Four bulk decisions match the original\n * single-edit modal contract; `partial` is the multi-edit branch — the\n * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.\n *\n * `partial.mask` is 1:1 with the call's hunks (in input order): `true`\n * means apply, `false` means deny. The gate handler walks the mask to\n * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the\n * approved subset and the renderer reconstructs the full per-hunk view\n * from the `<edit-outcomes>` annotation appended to the tool result.\n */\nexport type ApprovalDecision\n = | 'accept-once'\n | 'accept-session'\n | 'accept-safelist'\n | 'deny'\n | { kind: 'partial', mask: readonly boolean[] }\n\n/**\n * Identifies the caller behind an approval prompt — the parent agent or\n * a specific subagent (`child-N`). The modal's right-side title pins the\n * label so the user knows which agent issued the call when subagents\n * bubble their gates up through the parent's hook bus.\n */\nexport type ApprovalOriginator\n = | { kind: 'parent' }\n | { kind: 'child', label: string }\n\n/** A tool call waiting for the user's decision. */\nexport interface ApprovalRequest {\n /** Unique id of the request. */\n id: string\n /** Name of the tool awaiting approval. */\n tool: string\n /** The tool call's input. */\n input: Record<string, unknown>\n /** Settles the request with the user's decision. */\n resolve: (decision: ApprovalDecision) => void\n /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */\n originator?: ApprovalOriginator\n /** Model decision approvals: the decision model's verdict when it handed this call to you (`modelConcern`). */\n concern?: ModelApproval\n /**\n * Cross-window correlation id (the daemon's approval request id) for\n * entries surfaced by the daemon runtime. Lets `dropByCorrelation`\n * retract this entry when ANOTHER window resolves the same shared\n * approval, without re-sending a decision. Absent for local-runtime\n * approvals.\n */\n correlationId?: string\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (\n tool: string,\n input: Record<string, unknown>,\n originator?: ApprovalOriginator,\n correlationId?: string,\n) => Promise<ApprovalDecision>\n\n/** Functions that drive the approval queue. */\nexport interface SafeModeActions {\n /** Request a decision; resolves once the user picks. */\n requestApproval: RequestApproval\n /** Resolve the head and shift the queue forward. */\n resolveHead: (decision: ApprovalDecision) => void\n /** Resolve all pending with `deny`. Used on abort / hard exit. */\n denyAll: () => void\n /**\n * Remove the entry whose `correlationId` matches, WITHOUT resolving it\n * (the daemon already did, in another window). No-op when absent.\n */\n dropByCorrelation: (correlationId: string) => void\n}\n\nconst SafeModeQueueContext = createContext<readonly ApprovalRequest[]>([])\nconst SafeModeActionsContext = createContext<SafeModeActions | null>(null)\n\nlet approvalIdCounter = 0\nfunction nextApprovalId(): string {\n // Monotonic id is enough — uniqueness is scoped to a single TUI session\n // and the value never leaves the React tree. Avoids Date.now/Math.random\n // collisions inside tight parallel-tool batches.\n approvalIdCounter += 1\n return `approval-${approvalIdCounter}`\n}\n\n/**\n * Owns the queue + actions. Splits the value across two contexts so a queue\n * change doesn't invalidate every callback memo that closes over the actions.\n *\n * @param props - React props. `children` renders inside both contexts.\n */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\n\n // Mirror the live queue so the unmount cleanup settles pending approvals\n // without re-subscribing on every queue change.\n const queueRef = useRef<readonly ApprovalRequest[]>([])\n queueRef.current = queue\n\n // Provider unmount drops the queue state — a tool body parked on\n // `requestApproval` would then hang forever (its run's `running` gate\n // never clears). Deny every pending request on unmount so no resolver is\n // silently dropped. (#4)\n useEffect(() => () => {\n for (const p of queueRef.current)\n p.resolve('deny')\n }, [])\n\n // `setQueue` is stable, so `useCallback([])` here gives genuine identity\n // stability — `requestApproval` is the same function reference for the\n // lifetime of the provider, which is what hook handlers need.\n const requestApproval = useCallback<RequestApproval>(\n (tool, input, originator, correlationId) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [\n ...prev,\n {\n id: nextApprovalId(),\n tool,\n input,\n resolve,\n ...(originator ? { originator } : {}),\n ...(correlationId ? { correlationId } : {}),\n },\n ])\n }),\n [],\n )\n\n const resolveHead = useCallback((decision: ApprovalDecision) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(decision)\n return rest\n })\n }, [])\n\n const denyAll = useCallback(() => {\n setQueue((prev) => {\n for (const p of prev) p.resolve('deny')\n return []\n })\n }, [])\n\n const dropByCorrelation = useCallback((correlationId: string) => {\n // Remove WITHOUT resolving: the daemon already settled this approval\n // in the window that answered it. Resolving here would double-answer\n // (harmless deny) AND fire the RPC guard path unnecessarily.\n setQueue(prev => prev.filter(p => p.correlationId !== correlationId))\n }, [])\n\n // Actions object is stable across renders — its members are all `useCallback`'d\n // with `[]` deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useSafeModeActions()` never\n // re-render purely because of safe-mode state.\n const actionsRef = useRef<SafeModeActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { requestApproval, resolveHead, denyAll, dropByCorrelation }\n\n return (\n <SafeModeActionsContext.Provider value={actionsRef.current}>\n <SafeModeQueueContext.Provider value={queue}>\n {children}\n </SafeModeQueueContext.Provider>\n </SafeModeActionsContext.Provider>\n )\n}\n\n/** Returns the pending approval requests, oldest first. */\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\n/** Returns the approval actions. Throws when used outside {@link SafeModeProvider}. */\nexport function useSafeModeActions(): SafeModeActions {\n const ctx = useContext(SafeModeActionsContext)\n if (!ctx)\n throw new Error('useSafeModeActions must be used inside <SafeModeProvider>')\n return ctx\n}\n","import type { Dispatch, MutableRefObject, SetStateAction } from 'react'\nimport type { TranscriptWeight } from './streaming-pure'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { appendMarkdownDelta, appendThinkingLines, smoothCharsForTick, stepTranscriptCap, takeChars } from './streaming-pure'\n\n// Pure delta-merge / finalize reducers + context-window math live in the\n// node-free `./streaming-pure` leaf (importable from `zidane/chat/pure`, and\n// shared with the GUI renderer). Re-exported here so existing `zidane/chat`\n// consumers keep their import paths.\nexport { appendMarkdownDelta, appendThinkingLines, capTranscriptEvents, finalizeStreamingMarkdown, finalizeStreamingMarkdownForOwner, hiddenEarlierTranscriptCount, MAX_TRANSCRIPT_EVENTS, mergeTargetIndex, replaceTranscriptTurnEvents, revealEarlierTranscriptEvents, smoothCpsForBacklog, stepTranscriptCap, TRANSCRIPT_TRIM_STEP_EVENTS, TRANSCRIPT_TRIM_TO_EVENTS, turnContextSize } from './streaming-pure'\n\n/**\n * Default tick interval for the continuous drain loop. ~16ms aligns with\n * the 60fps renderer target so the typewriter cadence reveals 1 portion of\n * content per paint frame (a host whose frame rate differs passes\n * {@link StreamBufferOptions.getTickIntervalMs}). The bucket-derived\n * `chars-per-tick` decides how much actually lands per tick — see\n * {@link smoothCharsForTick}.\n */\nconst TICK_INTERVAL_MS = 16\n\n/**\n * Safety bound for {@link StreamBuffer.whenDrained} — per window of NO\n * drain progress. A long response legitimately types out for longer\n * than any fixed deadline (the ramp bottoms out at\n * the base rate, `SMOOTH_BASE_CPS` in `./streaming-pure`), so each committed tick re-arms the window;\n * only a genuinely wedged ticker — this long with nothing committed —\n * fires the escape hatch, and convergence (transcript resync) is never\n * blocked forever.\n */\nexport const WHEN_DRAINED_TIMEOUT_MS = 3000\n\nconst PARENT_OWNER: Owner = 'parent'\n\n/** One owner's (parent or a subagent) not-yet-committed deltas. */\ninterface DeltaBucket {\n markdown: string\n thinking: string\n owner: Owner\n depth: number\n /** Last turnId seen for this bucket. Tagged onto new events when the bucket flushes. */\n turnId?: string\n /**\n * Fractional chars carried over from the previous smooth tick. The\n * tick computes an exact float (`cps * dt`), takes the integer part,\n * and stashes the leftover here so the next tick adds it back in.\n * This makes the long-run rate match the target CPS exactly instead\n * of being inflated by `Math.ceil` rounding on every tick.\n */\n smoothCarry: number\n}\n\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth, smoothCarry: 0 }\n}\n\nfunction applyBucket(prev: StreamEvent[], bucket: DeltaBucket): StreamEvent[] {\n let result = prev\n if (bucket.thinking)\n result = appendThinkingLines(result, bucket.thinking, bucket.owner, bucket.depth, bucket.turnId)\n if (bucket.markdown)\n result = appendMarkdownDelta(result, bucket.markdown, bucket.owner, bucket.depth, bucket.turnId)\n return result\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — per-owner stream-event accumulator with two drain modes.\n//\n// Provider deltas arrive at ~50Hz; flushing each one through React causes the\n// ScrollBox to recompute layout faster than the terminal can clear and\n// repaint, producing overdraw artifacts during scroll. We buffer per owner\n// (parent + each active subagent) and drain via a continuous ticker:\n//\n// - **smooth mode** (default — gated on {@link Settings.smoothStreaming}):\n// drip-feed text character-by-character at the base rate (`./streaming-pure`), with\n// an adaptive burst when the buffer falls behind. Reads as a typewriter\n// effect and masks bursty provider chunks behind a steady cadence.\n// While markdown backlog remains, turn-boundary calls (`appendImmediate`\n// / `flushAndUpdate`) queue behind it and `flush` leaves the ticker to\n// finish. They apply once the backlog has typed out.\n// - **batched mode** (off): drain everything per tick.\n//\n// `getSmooth` is called on every tick so toggling the user setting at\n// runtime takes effect on the next drain without rebuilding the buffer.\n// ---------------------------------------------------------------------------\n\n/** Which agent and turn a streaming delta came from. */\nexport interface StreamSource {\n /** Pass `undefined` / omit for parent-agent events. */\n childId?: string\n /** Nesting depth — 0 for parent, ≥ 1 for subagents. */\n depth?: number\n /**\n * `SessionTurn.id` this delta belongs to. Tagged onto new events so the\n * TUI's select-turn mode can group every event from one turn. Stored on\n * the active bucket and refreshed on every delta so a turn boundary\n * (`turn:after` flushes the bucket) doesn't carry stale ids forward.\n */\n turnId?: string\n}\n\n/** Handle returned by {@link useStreamBuffer}. */\nexport interface StreamBuffer {\n /** Queue a streaming delta for the next flush tick. */\n queueStreamDelta: (kind: 'markdown' | 'thinking', delta: string, source?: StreamSource) => void\n /** Drain pending deltas immediately, then append a non-streaming event. */\n appendImmediate: (evt: StreamEvent) => void\n /** Drain pending deltas immediately, then transform the event list. */\n flushAndUpdate: (update: (events: StreamEvent[]) => StreamEvent[]) => void\n /** Drain pending deltas without further transformation. */\n flush: () => void\n /** Cancel any pending flush and drop buffered deltas (on session teardown). */\n reset: () => void\n /**\n * Resolves once the pacer is idle — no bucket holds queued content and\n * no post-drain updater is pending. Resolves immediately when already\n * idle (the batched / `smoothStreaming: false` path never queues, so\n * this is zero-cost there). A safety timeout (default\n * {@link WHEN_DRAINED_TIMEOUT_MS}) resolves anyway if the ticker\n * wedges — measured as a full window with NO drain progress, so a\n * long response that keeps typing re-arms it and never snaps — and\n * callers gating a transcript convergence on the drain can never\n * dead-lock. `reset()` also releases every waiter — the backlog it\n * drops would otherwise never drain.\n */\n whenDrained: (timeoutMs?: number) => Promise<void>\n}\n\n/** Options for {@link useStreamBuffer}. */\nexport interface StreamBufferOptions {\n /**\n * Returns `true` to drip-feed buffered text at the smooth typewriter\n * cadence, `false` to drain everything per tick (batched mode). Called on every tick, so flipping the user setting\n * takes effect on the next drain.\n */\n getSmooth?: () => boolean\n /**\n * Display-memory bound applied on every drain commit (see\n * {@link capTranscriptEvents}): above this many events the oldest are\n * trimmed at a turn boundary and replaced with a cumulative\n * \"… N earlier items hidden\" marker event, at most\n * {@link TRANSCRIPT_TRIM_STEP_EVENTS} per drain ({@link stepTranscriptCap}),\n * so a large overflow never freezes one frame. Rendered events hold live\n * renderables/native buffers, so an unbounded buffer OOMs long\n * sessions (~0.13MB RSS per event measured in the TUI).\n *\n * Default {@link MAX_TRANSCRIPT_EVENTS}. Pass `null` to disable and\n * receive the full, unbounded event array, for example for\n * a host that renders a virtualized list or exports events verbatim.\n */\n maxTranscriptEvents?: number | null\n /**\n * Live override of {@link maxTranscriptEvents}, read inside each drain\n * commit. Use it when the cap changes between renders: a pending drain\n * updater runs during the host's next `useState` evaluation, BEFORE this\n * hook re-mirrors the option, so a render-mirrored value is one commit\n * stale (a history reveal would be re-trimmed by that commit).\n */\n getMaxTranscriptEvents?: () => number | null | undefined\n /**\n * What each event weighs on screen ({@link TranscriptWeight}), read at\n * each drain commit like {@link getMaxTranscriptEvents}: the cap then\n * counts rows, not events. Without it every event is a row.\n */\n getTranscriptWeight?: () => TranscriptWeight | undefined\n /**\n * Drain tick interval in ms, read each time the ticker starts. Match it to\n * the host's frame interval: ticking faster than frames paint only adds\n * commits nobody sees. The smooth cadence measures real elapsed time, so\n * the character rate does not depend on it. Default 16 (60 fps).\n */\n getTickIntervalMs?: () => number | undefined\n}\n\n/**\n * Take this tick's portion off every live bucket (mutating the buckets):\n * all of its thinking, and its markdown at the smooth cadence or in full.\n * `stillHasContent` = some bucket kept markdown for a later tick.\n */\nfunction takeTickPortions(\n buckets: Map<Owner, DeltaBucket>,\n smooth: boolean,\n dtMs: number,\n): { portions: DeltaBucket[], stillHasContent: boolean } {\n const portions: DeltaBucket[] = []\n let stillHasContent = false\n for (const bucket of buckets.values()) {\n const thinking = bucket.thinking\n bucket.thinking = ''\n\n let taken = ''\n if (bucket.markdown.length > 0) {\n if (smooth) {\n const { take, carry } = smoothCharsForTick(\n bucket.markdown.length,\n bucket.smoothCarry,\n dtMs,\n )\n bucket.smoothCarry = carry\n if (take > 0) {\n const result = takeChars(bucket.markdown, take)\n taken = result.taken\n bucket.markdown = result.rest\n if (bucket.markdown.length === 0)\n bucket.smoothCarry = 0\n }\n }\n else {\n taken = bucket.markdown\n bucket.markdown = ''\n bucket.smoothCarry = 0\n }\n }\n\n if (taken || thinking) {\n portions.push({\n markdown: taken,\n thinking,\n owner: bucket.owner,\n depth: bucket.depth,\n turnId: bucket.turnId,\n smoothCarry: 0,\n })\n }\n if (bucket.markdown.length > 0)\n stillHasContent = true\n }\n return { portions, stillHasContent }\n}\n\n/** No queued post-drain updater and no bucket holding content. */\nfunction bufferIdle(buckets: Map<Owner, DeltaBucket>, pendingUpdaters: number): boolean {\n let idle = pendingUpdaters === 0\n if (idle) {\n for (const bucket of buckets.values()) {\n if (bucket.markdown.length > 0 || bucket.thinking.length > 0) {\n idle = false\n break\n }\n }\n }\n return idle\n}\n\ninterface DrainWaiter { resolve: () => void, timer: ReturnType<typeof setTimeout> }\n\n/** Register a `whenDrained` waiter, with its no-progress safety timer armed. */\nfunction addDrainWaiter(\n resolve: () => void,\n drainProgressRef: MutableRefObject<number>,\n drainWaitersRef: MutableRefObject<DrainWaiter[]>,\n timeoutMs: number,\n): void {\n // Safety valve: a wedged ticker (cleared interval, starved event\n // loop) must not block the caller forever — resolve anyway and\n // drop the waiter so a late drain doesn't double-resolve. Armed\n // per window of NO progress: a tick that committed content since\n // the last check proves the drain is alive (a long response types\n // for longer than any fixed deadline), so re-arm instead of\n // snapping the tail into the caller's convergence.\n let seenProgress = drainProgressRef.current\n const waiter = {\n resolve,\n timer: undefined as unknown as ReturnType<typeof setTimeout>,\n }\n const arm = (): ReturnType<typeof setTimeout> => setTimeout(() => {\n if (drainProgressRef.current !== seenProgress) {\n seenProgress = drainProgressRef.current\n waiter.timer = arm()\n return\n }\n drainWaitersRef.current = drainWaitersRef.current.filter(w => w !== waiter)\n resolve()\n }, timeoutMs)\n waiter.timer = arm()\n drainWaitersRef.current.push(waiter)\n}\n\n/**\n * Buffers streaming deltas and commits them to the transcript on a ticker, in smooth or batched mode.\n *\n * @param setEvents - State setter for the transcript events.\n * @param options - Cadence and transcript cap settings.\n */\nexport function useStreamBuffer(\n setEvents: Dispatch<SetStateAction<StreamEvent[]>>,\n options?: StreamBufferOptions,\n): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null)\n // Real wall-clock timestamp of the last tick. Feeds the dt argument\n // to `smoothCharsForTick` so the drain rate stays accurate even when\n // `setInterval` is late (event-loop congestion, GC pauses); without\n // this a 50ms-late tick would only commit ~16ms-worth of chars and\n // the visible cadence would stall.\n const lastTickMsRef = useRef<number>(0)\n // Pin the latest `getSmooth` callback inside a ref so the ticker closure\n // always sees the current setting without recreating the timer.\n const getSmoothRef = useRef<(() => boolean) | undefined>(options?.getSmooth)\n getSmoothRef.current = options?.getSmooth\n /** Live mirror so drains read the current cap without re-memoizing. */\n const maxEventsRef = useRef<number | null | undefined>(options?.maxTranscriptEvents)\n maxEventsRef.current = options?.maxTranscriptEvents\n const getMaxEventsRef = useRef(options?.getMaxTranscriptEvents)\n getMaxEventsRef.current = options?.getMaxTranscriptEvents\n const getWeightRef = useRef(options?.getTranscriptWeight)\n getWeightRef.current = options?.getTranscriptWeight\n const getTickIntervalRef = useRef(options?.getTickIntervalMs)\n getTickIntervalRef.current = options?.getTickIntervalMs\n /** Interval of the running ticker (the nominal first-tick dt and the dt clamp). */\n const tickIntervalRef = useRef(TICK_INTERVAL_MS)\n const capForCommit = useCallback((events: StreamEvent[]): StreamEvent[] => {\n const cap = getMaxEventsRef.current ? getMaxEventsRef.current() : maxEventsRef.current\n // Stepped: a big overflow (a reveal handed back) trims over several drains.\n return cap === null ? events : stepTranscriptCap(events, cap ?? undefined, undefined, getWeightRef.current?.())\n }, [])\n\n /**\n * Updaters queued while smooth-streaming was still draining backlog —\n * tool-call appends, finalize-markdown transforms, error events.\n * Applied in FIFO order at the END of `tick()` once every bucket is\n * empty, so the trailing typewriter characters land BEFORE the\n * follow-up event instead of being clobbered by an immediate drain.\n *\n * The buffer treats markdown deltas as the authoritative ordering\n * source: a tool call queued behind 100 buffered chars surfaces after\n * those 100 chars have typed out, preserving the visual stream order\n * the user is reading.\n */\n const pendingUpdatersRef = useRef<((events: StreamEvent[]) => StreamEvent[])[]>([])\n\n /**\n * Pending `whenDrained` promises. Released (with their safety timers\n * cleared) whenever the buffer reaches the idle state: the tick that\n * empties the last bucket, a synchronous `drainNow`, or a `reset`.\n */\n const drainWaitersRef = useRef<DrainWaiter[]>([])\n\n /**\n * Monotonic count of commits (ticks that landed content or updaters).\n * `whenDrained` waiters compare it across their safety window: movement\n * means the ticker is alive — re-arm instead of firing the escape hatch.\n */\n const drainProgressRef = useRef(0)\n\n const releaseDrainWaiters = useCallback(() => {\n if (drainWaitersRef.current.length === 0)\n return\n const waiters = drainWaitersRef.current\n drainWaitersRef.current = []\n for (const waiter of waiters) {\n clearTimeout(waiter.timer)\n waiter.resolve()\n }\n }, [])\n\n const stopTicker = useCallback(() => {\n if (tickerRef.current) {\n clearInterval(tickerRef.current)\n tickerRef.current = null\n }\n // Forget the last-tick stamp so the next ticker run starts from\n // the nominal interval instead of a stale (potentially seconds-old)\n // delta that would burst-drain the first frame.\n lastTickMsRef.current = 0\n }, [])\n\n /**\n * Has at least one bucket got unflushed *markdown* content?\n *\n * Thinking content is excluded: it always flushes in full on every\n * tick (see `tick()`), so its presence in a bucket only matters until\n * the next 16 ms cycle — never long enough to justify deferring an\n * end-of-turn updater.\n */\n const hasMarkdownBacklog = useCallback((): boolean => {\n for (const bucket of bucketsRef.current.values()) {\n if (bucket.markdown.length > 0)\n return true\n }\n return false\n }, [])\n\n /**\n * Drain every bucket in full and synchronously run any queued\n * post-drain updaters plus the caller-provided one. Used by the\n * fast paths below: `flush`, and `appendImmediate` /\n * `flushAndUpdate` in batched mode or when no markdown backlog remains.\n */\n const drainNow = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n stopTicker()\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const queued = pendingUpdatersRef.current\n pendingUpdatersRef.current = []\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater && queued.length === 0) {\n releaseDrainWaiters()\n return\n }\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n for (const u of queued)\n merged = u(merged)\n if (updater)\n merged = updater(merged)\n // Memory bound: identity under the cap, turn-boundary head trim\n // above it (see capTranscriptEvents). Session turns stay complete.\n return capForCommit(merged)\n })\n releaseDrainWaiters()\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n /**\n * One tick of the continuous drain loop. Walks every live bucket and\n * commits a portion of its content based on the current mode. Once\n * every bucket empties out, fires any post-drain updaters that were\n * queued while smooth streaming was still in progress (turn\n * finalize, tool calls, errors) and stops the ticker.\n *\n * `thinking` content always flushes immediately even in smooth mode —\n * it's an internal-reasoning surface, surfaced for transparency rather\n * than for reading flow, and users expect it to keep up with the model.\n */\n const tick = useCallback(() => {\n const smooth = getSmoothRef.current?.() ?? true\n const buckets = bucketsRef.current\n\n // Real elapsed time since the last tick. Falls back to the nominal\n // interval on the first tick (when there's nothing to measure\n // against) and clamps to a sane upper bound so a one-off 500ms\n // event-loop stall doesn't burst-drain the entire backlog.\n const now = Date.now()\n const interval = tickIntervalRef.current\n const dtMs = lastTickMsRef.current === 0\n ? interval\n : Math.min(interval * 4, Math.max(1, now - lastTickMsRef.current))\n lastTickMsRef.current = now\n\n const { portions, stillHasContent } = takeTickPortions(buckets, smooth, dtMs)\n\n // Fold post-drain updaters into the same commit as the trailing\n // portions so the resulting transcript has at most one paint per\n // tick — and so the LAST portion of a smooth stream and its\n // companion finalize-markdown / tool-call event land atomically.\n const drainQueued = !stillHasContent && pendingUpdatersRef.current.length > 0\n const queued = drainQueued\n ? pendingUpdatersRef.current.splice(0, pendingUpdatersRef.current.length)\n : []\n\n if (portions.length > 0 || queued.length > 0) {\n drainProgressRef.current += 1\n setEvents((prev) => {\n let next = prev\n for (const portion of portions)\n next = applyBucket(next, portion)\n for (const u of queued)\n next = u(next)\n // Same memory bound as `drainNow`: deferred updaters append\n // tool-call / error events too, so this commit path must not\n // become a cap-bypass window. Identity under the cap.\n return capForCommit(next)\n })\n }\n\n if (!stillHasContent) {\n stopTicker()\n // Buckets and queued updaters both landed in this commit — the\n // buffer is idle, so end-of-stream convergence can proceed.\n releaseDrainWaiters()\n }\n }, [capForCommit, releaseDrainWaiters, setEvents, stopTicker])\n\n const ensureTicker = useCallback(() => {\n if (tickerRef.current)\n return\n const requested = getTickIntervalRef.current?.()\n tickIntervalRef.current = requested !== undefined && Number.isFinite(requested) && requested > 0 ? requested : TICK_INTERVAL_MS\n tickerRef.current = setInterval(tick, tickIntervalRef.current)\n }, [tick])\n\n /**\n * Decide between fast-drain and deferred-drain for an\n * updater-bearing call (`appendImmediate`, `flushAndUpdate`).\n *\n * Smooth mode + markdown backlog → ENQUEUE the updater behind the\n * trailing characters; the ticker picks it up once buckets empty.\n * Anything else → fall through to `drainNow` so the call retains\n * its synchronous semantics.\n */\n const drainOrDefer = useCallback((updater: (events: StreamEvent[]) => StreamEvent[]) => {\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n pendingUpdatersRef.current.push(updater)\n ensureTicker()\n return\n }\n drainNow(updater)\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flush = useCallback(() => {\n // In smooth mode with backlog, the ticker is already draining at\n // the typewriter cadence — explicit `flush()` shouldn't yank the\n // remaining characters into a single frame. The ticker is\n // self-stopping once empty, so this becomes a no-op there.\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n ensureTicker()\n return\n }\n drainNow()\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainOrDefer(update),\n [drainOrDefer],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainOrDefer(events => [...events, evt]),\n [drainOrDefer],\n )\n\n const queueStreamDelta = useCallback((\n kind: 'markdown' | 'thinking',\n delta: string,\n source?: StreamSource,\n ) => {\n if (!delta)\n return\n const owner: Owner = source?.childId ?? PARENT_OWNER\n const depth = source?.depth ?? 0\n let bucket = bucketsRef.current.get(owner)\n if (!bucket) {\n bucket = emptyBucket(owner, depth)\n bucketsRef.current.set(owner, bucket)\n }\n bucket[kind] += delta\n // Refresh on every delta — `turn:after` drains the bucket between turns,\n // so a turnId only ever applies to the deltas of one turn. Keeping the\n // latest also lets a turn that starts mid-bucket (rare but theoretically\n // possible if two hooks race the flush ticker) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n ensureTicker()\n }, [ensureTicker])\n\n const reset = useCallback(() => {\n stopTicker()\n bucketsRef.current.clear()\n pendingUpdatersRef.current = []\n // The dropped backlog will never drain — release waiters instead of\n // stranding them until the safety timeout.\n releaseDrainWaiters()\n }, [releaseDrainWaiters, stopTicker])\n\n const whenDrained = useCallback((timeoutMs: number = WHEN_DRAINED_TIMEOUT_MS): Promise<void> => {\n if (bufferIdle(bucketsRef.current, pendingUpdatersRef.current.length))\n return Promise.resolve()\n return new Promise(resolve => addDrainWaiter(resolve, drainProgressRef, drainWaitersRef, timeoutMs))\n }, [])\n\n // Unmount hygiene: the interval ticker outlives the render otherwise and\n // fires `setEvents` against an unmounted host. `reset` is stable (all\n // deps are useCallback'd), so this registers exactly once and never\n // perturbs the memoized return below.\n useEffect(() => reset, [reset])\n\n // CRITICAL: stabilize the returned object. All six callbacks are useCallback'd\n // (stable across renders), but a bare object-literal return would be a fresh\n // reference every call, which cascades through every `useCallback`/`useEffect`\n // that depends on `stream` and turns the resume effect into an unbounded\n // destroy/rebuild loop — the exact source of the terminal-host memory leak.\n return useMemo(\n () => ({ queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset, whenDrained],\n )\n}\n","import type { ReactNode } from 'react'\nimport type { SyntaxStyles, Theme, ThemeColors, ThemeSelect, ThemeSurfaces } from './theme'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// ThemeContext\n//\n// The TUI / GUI mounts `<ThemeProvider theme={…}>` near the root of its tree,\n// resolving the active theme from `Settings.theme`. Every component reads\n// theme values through a hook so a runtime theme switch (Settings → Theme)\n// triggers an immediate re-paint of the whole tree.\n//\n// Sub-hooks (`useColors`, `useSelectStyle`, …) exist so consumers can grab\n// just the slice they need, for example `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\n/**\n * Provides `theme` to the subtree.\n *\n * @param props - The `theme` to provide and the `children` to render.\n */\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\n/** Returns the current theme. Falls back to {@link DEFAULT_THEME} outside a {@link ThemeProvider}. */\nexport function useTheme(): Theme {\n return useContext(ThemeContext)\n}\n\n/** Color palette only — equivalent to `useTheme().colors`. */\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n\n/** Select-row styling — equivalent to `useTheme().select`: the `▶` marker and accent text carry the cursor. */\nexport function useSelectStyle(): ThemeSelect {\n return useContext(ThemeContext).select\n}\n\n/** Panel / surface backgrounds — equivalent to `useTheme().surfaces`. */\nexport function useSurfaces(): ThemeSurfaces {\n return useContext(ThemeContext).surfaces\n}\n\n/** Raw syntax style table — `useTheme().syntax`. Renderer converts to its native style type. */\nexport function useSyntaxStyles(): SyntaxStyles {\n return useContext(ThemeContext).syntax\n}\n","// ---------------------------------------------------------------------------\n// Transcript scroll-anchor computation.\n//\n// Walks a list of partitioned transcript items (one entry per row that\n// will be rendered) and assigns a stable scroll-anchor id to the FIRST\n// event of each turn — `turn-anchor-<turnId>`. The renderer applies\n// these ids to whichever container hosts the first event of the turn;\n// scroll-into-view (TUI scrollbox or GUI element.scrollIntoView) then\n// resolves the id to a known target.\n//\n// Pure data-in / data-out so the same logic powers the TUI today and\n// any future GUI without dragging OpenTUI specifics into the chat\n// layer.\n// ---------------------------------------------------------------------------\n\nimport type { StreamEvent } from './types'\n\n/**\n * One entry in the partitioned transcript-row list. Single events\n * render as a normal row; a `child-run` groups ONE subagent's events\n * from a consecutive run of subagent output into one bordered box\n * (concurrent siblings get a `child-run` each, so their rows never\n * braid together inside a shared frame); `tool-group`s\n * collect ALL parent-owned tool/tool-result events of one exchange\n * into a single collapsible summary row (`Settings.groupToolCalls`);\n * an `exchange-fold` goes further (`Settings.transcriptDensity:\n * 'compact'`) and takes every thinking, tool and worker event of the\n * exchange, leaving only the prompt and the assistant's prose in place.\n * `key` is stable across re-partitions of the same stream (derived\n * from the first grouped event) so expand state survives appends.\n *\n * Currently produced by the TUI's `partitionTranscript`; a GUI shell\n * grouping subagent runs the same way can produce the same shape.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'tool-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n | { kind: 'exchange-fold', key: string, events: StreamEvent[], previous?: StreamEvent }\n /** Compact density: an exchange's earlier messages, ahead of its last one. */\n | { kind: 'message-group', key: string, events: StreamEvent[], previous?: StreamEvent }\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order\n * and, for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scroll target).\n * - `undefined` — later event of an already-tagged turn (or a\n * synthetic event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N\n * for subagent runs (one entry per inner event). `idByTurn` is the\n * inverse lookup used by the scroll effect. `lastTurnId` is the\n * most-recently-rendered turn — the scroll effect special-cases it to\n * snap to bottom rather than scroll the anchor into view (which would\n * stop short of the actual tail).\n *\n * Exported so the anchor-tagging matrix can be unit-tested without\n * rendering anything.\n *\n * @param items - Transcript items in render order.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n"],"mappings":";;;;;;;;AAsBA,MAAM,UAAU,CAAC,CAAC,QAAQ,IAAI;;;;;;;;AAQ9B,MAAM,QAAQ,YAAY,IAAI;AAC9B,IAAI,OAAO;;;;;;;;;;;;AAaX,SAAgB,SAAS,OAAqB;CAC5C,IAAI,CAAC,SACH;CACF,MAAM,MAAM,YAAY,IAAI;CAC5B,MAAM,QAAQ,MAAM;CACpB,MAAM,QAAQ,MAAM;CACpB,OAAO;CAGP,QAAQ,OAAO,MACb,WAAW,MAAM,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,aAAa,MAAM,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,OAAO,MAAM,GACjG;AACF;;;;;;AAOA,SAAgB,qBAA8B;CAC5C,OAAO;AACT;;;;;;;;AC1BA,MAAM,aAAsD,OAAO,OAAO,CAAC,CAAC;;;;;;;;;;;;;;;;;AA+C5E,SAAgB,cACd,OACA,WACA,UAAuC,CAAC,GAChB;CACxB,MAAM,EAAE,MAAM,WAAW;CAIzB,MAAM,EAAE,mBAAmB;CAS3B,MAAM,CAAC,uBAAuB,4BAA4B,SAAwB,IAAI;CAEtF,MAAM,aAAa,cACX,mBAAmB,MAAM,QAAQ,WAAW,EAAE,eAAe,CAAC,GACpE;EAAC;EAAM;EAAQ;EAAW;CAAc,CAC1C;CAEA,MAAM,cAAc,cAAc;EAChC,IAAI,CAAC,YACH,OAAO;EACT,IAAI,WAAW,KAAK,UAAU,uBAC5B,OAAO;EACT,OAAO;CACT,GAAG,CAAC,YAAY,qBAAqB,CAAC;CAKtC,MAAM,SAAS,cACP,cACF;EAAE,UAAU,YAAY,UAAU;EAAI,OAAO,YAAY;EAAO,MAAM,YAAY;CAAK,IACvF,MACJ,CAAC,WAAW,CACd;CAMA,gBAAgB;EACd,IAAI,CAAC,YACH,yBAAyB,IAAI;CACjC,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,CAAC,OAAO,YAAY,SAA2C,CAAC,CAAC;CACvE,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,CAAC,eAAe,oBAAoB,SAAS,CAAC;CAGpD,MAAM,WAAW,OAA+B,IAAI;CAEpD,gBAAgB;EACd,SAAS,SAAS,MAAM;EACxB,IAAI,CAAC,aAAa;GAChB,SAAS,CAAC,CAAC;GACX,WAAW,KAAK;GAChB,iBAAiB,CAAC;GAClB;EACF;EACA,MAAM,aAAa,IAAI,gBAAgB;EACvC,SAAS,UAAU;EACnB,MAAM,MAAyB;GAAE;GAAM;EAAO;EAC9C,IAAI,YAAY;EAChB,MAAM,OAAO,YAAY,UAAU,KAAI,cAAa;GAClD;GACA,KAAK,SAAS,QAAQ,YAAY,OAAO,KAAK,WAAW,MAAM;EACjE,EAAE;EAGF,IAAI,KAAK,OAAM,MAAK,MAAM,QAAQ,EAAE,GAAG,CAAC,GAAG;GACzC,SAAS,iBAAiB,KAAK,KAAI,OAAM;IAAE,UAAU,EAAE;IAAU,OAAO,EAAE;GAA+B,EAAE,GAAG,YAAY,UAAU,MAAM,CAAC;GAC3I,iBAAiB,CAAC;GAClB,WAAW,KAAK;GAChB;EACF;EACA,WAAW,IAAI;EAKf,QAAa,WAAW,KAAK,IAAI,OAAM,OAAM;GAAE,UAAU,EAAE;GAAU,OAAO,MAAM,EAAE;EAAI,EAAE,CAAC,CAAC,CAAC,MAC1F,YAAY;GACX,IAAI,WACF;GACF,SAAS,iBAAiB,QAAQ,SAAQ,MAAK,EAAE,WAAW,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,MAAM,CAAC;GACxH,iBAAiB,CAAC;GAClB,WAAW,KAAK;EAClB,CACF;EACA,aAAa;GACX,YAAY;GACZ,WAAW,MAAM;EACnB;CACF,GAAG;EAAC;EAAa;EAAM;CAAM,CAAC;CAc9B,MAAM,aAAa,cACX;EACJ,MAAM,OAAO,kBAAkB,MAAM,WAAW,MAAM;EACtD,OAAO,KAAK,WAAW,IAAK,aAAuD;CACrF,GACA,CAAC,MAAM,SAAS,CAClB;CAEA,MAAM,aAAa,kBAAkB;EACnC,kBAAiB,MAAM,MAAM,WAAW,IAAI,KAAK,IAAI,KAAK,MAAM,MAAO;CACzE,GAAG,CAAC,MAAM,MAAM,CAAC;CACjB,MAAM,aAAa,kBAAkB;EACnC,kBAAiB,MAAM,MAAM,WAAW,IAAI,KAAK,IAAI,IAAI,MAAM,UAAU,MAAM,MAAO;CACxF,GAAG,CAAC,MAAM,MAAM,CAAC;CACjB,MAAM,UAAU,kBAAkB;EAGhC,IAAI,YACF,yBAAyB,WAAW,KAAK,KAAK;CAClD,GAAG,CAAC,UAAU,CAAC;CACf,MAAM,SAAS,kBAA2D;EACxE,IAAI,CAAC,UAAU,MAAM,WAAW,GAC9B,OAAO;EACT,MAAM,OAAO,MAAM,KAAK,IAAI,eAAe,MAAM,SAAS,CAAC;EAC3D,OAAO,YAAY,MAAM,OAAO,MAAM,KAAK,UAAU;CACvD,GAAG;EAAC;EAAQ;EAAO;EAAe;CAAI,CAAC;CAKvC,OAAO,eACE;EAAE;EAAQ;EAAO;EAAS;EAAe;EAAY;EAAY;EAAQ;EAAS;CAAW,IACpG;EAAC;EAAQ;EAAO;EAAS;EAAe;EAAY;EAAY;EAAQ;EAAS;CAAU,CAC7F;AACF;;;;ACtNA,MAAa,mCAAmC;AAoBhD,MAAM,uBAAuB;;AAG7B,MAAM,eAAkD;CACtD,cAAc;EAAC;EAAM;EAAQ;CAAI;CACjC,QAAQ,CAAC,UAAU,IAAI;CACvB,QAAQ,CAAC,UAAU,KAAK;CACxB,MAAM,CAAC,QAAQ,MAAM;AACvB;AAEA,SAAS,WAAW,OAAuC;CACzD,OAAO,MAAM,IAAI,QAAQ,MAAM,SAAS;AAC1C;AAEA,SAAS,SAAS,OAAuC;CACvD,MAAM,UAAU,aAAa,MAAM,SAAS,SAAS,CAAC,MAAM,SAAS,IAAI;CACzE,OAAO;EAAC,MAAM,IAAI;EAAO,MAAM,IAAI,YAAY;EAAI,MAAM,SAAS;EAAO,MAAM;EAAW,GAAG;CAAO,CAAC,CAAC,KAAK,GAAG;AAChH;AAEA,SAAS,eAAe,OAAuC;CAC7D,MAAM,OAAO,CAAC,MAAM,SAAS;CAC7B,IAAI,MAAM,IAAI,QACZ,KAAK,KAAK,MAAM,IAAI,MAAM;CAC5B,KAAK,KAAK,UAAU,MAAM,SAAS,SAAS,CAAC;CAC7C,MAAM,MAAM,WAAW,KAAK;CAC5B,IAAI,QAAQ,MAAM,IAAI,OACpB,KAAK,KAAK,GAAG;CACf,OAAO,KAAK,KAAK,IAAI;AACvB;;;;;;;;;AAUA,SAAgB,eACd,SACA,OACA,QAAQ,sBACiC;CACzC,MAAM,IAAI,MAAM,KAAK,CAAC,CAAC,YAAY;CACnC,MAAM,QAAQ,EAAE,SAAS,IACrB,IAAI,IAAI,IAAI,IAAI,SAAS;EAAE,UAAU;EAAU,QAAQ;CAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,IACrG;CACJ,MAAM,SAA4D,CAAC;CACnE,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,EAAE,WAAW,GAAG;GAClB,OAAO,KAAK;IAAE;IAAO,MAAM;GAAE,CAAC;GAC9B;EACF;EACA,MAAM,QAAQ,MAAM,IAAI,MAAM,YAAY;EAC1C,MAAM,WAAW,aAAa,MAAM,SAAS,SAAS,CAAC,MAAM,SAAS,IAAI,EAAA,CAAG,KAAI,MAAK,EAAE,YAAY,CAAC;EACrG,MAAM,YAAY,MAAM,UAAU,YAAY;EAC9C,IAAI,QAAQ,MAAK,MAAK,MAAM,KAAK,EAAE,WAAW,CAAC,CAAC,KAAK,UAAU,WAAW,CAAC,GAAG;GAC5E,OAAO,KAAK;IAAE;IAAO,MAAM;GAAE,CAAC;GAC9B;EACF;EACA,IAAI,MAAM,WAAW,CAAC,GAAG;GACvB,OAAO,KAAK;IAAE;IAAO,MAAM;GAAE,CAAC;GAC9B;EACF;EACA,IAAI,MAAM,SAAS,CAAC,KAAK,WAAW,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,GAAG;GACpE,OAAO,KAAK;IAAE;IAAO,MAAM;GAAE,CAAC;GAC9B;EACF;EACA,IAAI,OAAO,IAAI,KAAK,GAClB,OAAO,KAAK;GAAE;GAAO,MAAM;EAAE,CAAC;CAClC;CACA,OAAO,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,YAAY,EAAE,MAAM,SAAS,SAAS;CAChG,OAAO,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,KAA4C,EAAE,YAAY;EACtF,MAAM,UAAU,WAAW,KAAK;EAChC,OAAO;GACL,IAAI,YAAY,MAAM,SAAS;GAC/B,OAAO,MAAM,IAAI;GACjB,aAAa,eAAe,KAAK;GACjC,YAAY,GAAG,QAAQ;GACvB,MAAM;IAAE;IAAO;GAAQ;EACzB;CACF,CAAC;AACH;;;;;;;AAQA,SAAgB,kCAAkC,MAIJ;CAC5C,MAAM,QAAQ,KAAK,SAAS;CAC5B,OAAO;EACL,IAAI;EACJ,SAAS;EACT,OAAO;EACP,QAAQ,OAAO;GACb,MAAM,UAAU,KAAK,WAAW;GAChC,IAAI,QAAQ,SAAS,KAAK,CAAC,KAAK,eAC9B,OAAO,eAAe,SAAS,OAAO,KAAK;GAC7C,OAAO,KAAK,cAAc,CAAC,CAAC,MAAK,WAAU,eAAe,QAAQ,OAAO,KAAK,CAAC;EACjF;EAEA,uBAAuB,CAAC;CAC1B;AACF;;;;AC1IA,MAAa,gBAAgB,cAAqC,IAAI;;;;;;AAOtE,SAAgB,eAAe,EAAE,QAAQ,YAA6D;CACpG,OAAO,oBAAC,cAAc,UAAf;EAAwB,OAAO;EAAS;CAAiC,CAAA;AAClF;;AAGA,SAAgB,YAA4B;CAC1C,MAAM,MAAM,WAAW,aAAa;CACpC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,gDAAgD;CAClE,OAAO;AACT;;;ACuDA,MAAM,mBAAmB,cAA4C,IAAI;;;;;;AAOzE,SAAgB,kBAAkB,EAChC,OACA,YAIC;CACD,OACE,oBAAC,iBAAiB,UAAlB;EAAkC;EAC/B;CACwB,CAAA;AAE/B;;;;;;;;AASA,SAAgB,eAAsC;CACpD,MAAM,MAAM,WAAW,gBAAgB;CACvC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,sDAAsD;CACxE,OAAO;AACT;;;;;;;;AASA,SAAgB,uBAAqD;CACnE,OAAO,WAAW,gBAAgB;AACpC;;;;;;;;;ACrDA,SAAgB,oBAAuB,SAAoD;CACzF,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,YAAY,IAAI,gBAAgB;CACtC,IAAI,YAA0C;CAC9C,IAAI,aAAmC;CACvC,IAAI,gBAAgB;CACpB,IAAI,UAAU;CAEd,MAAM,eAAe,KAAc,UAA0C;EAC3E,QAAQ,UAAU,KAAK,KAAK;CAC9B;CAEA,MAAM,qBAAoC;EACxC,gBAAgB,KAAK,IAAI;EACzB,MAAM,MAAM,QAAQ,KAAK,UAAU,MAAM,CAAC,CACvC,MAAM,UAAU;GACf,IAAI,SACF;GACF,QAAQ,OAAO,KAAK;EACtB,CAAC,CAAC,CACD,OAAO,QAAQ;GACd,IAAI,SACF;GACF,YAAY,KAAK,SAAS;EAC5B,CAAC,CAAC,CACD,cAAc;GACb,IAAI,eAAe,KACjB,aAAa;EACjB,CAAC;EACH,aAAa;EACb,OAAO;CACT;CAEA,MAAM,OAAyB;EAC7B,SAAS;GACP,IAAI,SACF,OAAO,QAAQ,QAAQ,CAAC,CAAiB;GAC3C,IAAI,CAAC,WAAW;IACd,gBAAgB,KAAK,IAAI;IACzB,YAAY,QAAQ,KAAK,UAAU,MAAM,CAAC,CACvC,MAAM,UAAU;KACf,IAAI,SACF,OAAO,CAAC;KACV,QAAQ,OAAO,KAAK;KACpB,OAAO;IACT,CAAC,CAAC,CACD,OAAO,QAAQ;KACd,IAAI,CAAC,SACH,YAAY,KAAK,YAAY;KAC/B,OAAO,CAAC;IACV,CAAC;IACH,OAAO;GACT;GAEA,IAAI,CAAC,cAAc,KAAK,IAAI,IAAI,iBAAiB,YAC/C,aAAkB;GACpB,OAAO;EACT;EACA,UAAU;GACR,IAAI,SACF,OAAO,QAAQ,QAAQ;GACzB,IAAI,YACF,OAAO;GAKT,IAAI,CAAC,WACH,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC;GACpC,OAAO,aAAa;EACtB;EACA,eAAe;GACb,OAAO,eAAe;EACxB;EACA,QAAQ;GACN,IAAI,SACF;GACF,UAAU;GACV,UAAU,MAAM;EAClB;CACF;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3HA,SAAgB,mBAAmB,MAAwB;CACzD,MAAM,SAAmB,CAAC;CAC1B,IAAI,UAAU;CAGd,IAAI,QAA2B;CAG/B,IAAI,SAAS;CAEb,KAAK,MAAM,MAAM,MAAM;EACrB,IAAI,OAAO;GACT,IAAI,OAAO,OACT,QAAQ;QAER,WAAW;GACb;EACF;EACA,IAAI,OAAO,QAAO,OAAO,KAAM;GAC7B,QAAQ;GACR,SAAS;GACT;EACF;EACA,IAAI,OAAO,OAAO,OAAO,OAAQ,OAAO,QAAQ,OAAO,MAAM;GAC3D,IAAI,YAAY,MAAM,QAAQ;IAC5B,OAAO,KAAK,OAAO;IACnB,UAAU;IACV,SAAS;GACX;GACA;EACF;EACA,WAAW;CACb;CACA,IAAI,YAAY,MAAM,QACpB,OAAO,KAAK,OAAO;CACrB,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,cAAc,OAAuB;CACnD,IAAI,UAAU,IACZ,OAAO;CACT,IAAI,CAAC,iBAAiB,KAAK,KAAK,GAC9B,OAAO;CACT,OAAO,IAAI,MAAM,QAAQ,MAAM,MAAI,EAAE;AACvC;;;ACnEA,MAAM,kBAAkB,cAA2C,IAAI;;;;;;AAOvE,SAAgB,iBAAiB,EAC/B,SACA,UACA,YAKC;CAGD,MAAM,CAAC,UAAU,eAAe,eAAyB,0BAA0B,OAAO,CAAC;CAe3F,MAAM,cAAc,OAAO,QAAQ;CACnC,YAAY,UAAU;CACtB,MAAM,eAAe,OAAO,IAAI;CAChC,gBAAgB;EACd,IAAI,aAAa,SAAS;GACxB,aAAa,UAAU;GACvB;EACF;EACA,YAAY,UAAU,QAAQ;CAChC,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,SAAS,aAAa,QAA2B;EACrD,aAAY,SAAQ,0BAA0B;GAAE,GAAG;IAAO,MAAM,CAAC,KAAK;EAAK,CAAC,CAAC;CAC/E,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,aAAuC,KAAQ,UAAuB;EACvF,aAAY,SAAS,KAAK,SAAS,QAAQ,OAAO,0BAA0B;GAAE,GAAG;IAAO,MAAM;EAAM,CAAC,CAAE;CACzG,GAAG,CAAC,CAAC;CAEL,MAAM,QAAQ,eACL;EAAE;EAAU;EAAQ;CAAW,IACtC;EAAC;EAAU;EAAQ;CAAU,CAC/B;CAEA,OAAO,oBAAC,gBAAgB,UAAjB;EAAiC;EAAQ;CAAmC,CAAA;AACrF;;;;;;;AAQA,SAAgB,iBAAiB,EAAE,OAAO,YAAkE;CAC1G,OAAO,oBAAC,gBAAgB,UAAjB;EAAiC;EAAQ;CAAmC,CAAA;AACrF;;AAGA,SAAgB,cAAoC;CAClD,MAAM,MAAM,WAAW,eAAe;CACtC,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oDAAoD;CACtE,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrEA,SAAgB,oBAAuB,MAYlB;CACnB,MAAM,EAAE,UAAU,eAAe,YAAY;CAC7C,MAAM,EAAE,SAAS,OAAO,YAAY,WAAW;CAC/C,MAAM,YAAY,SAAS;CAE3B,MAAM,aAAa,cAAmC;EACpD,IAAI,cAAc,KAAA,GAChB,OAAO,IAAI,IAAI,QAAQ,IAAI,KAAK,CAAC;EACnC,OAAO,IAAI,IAAI,SAAS;CAC1B,GAAG;EAAC;EAAW;EAAS;CAAK,CAAC;CAS9B,MAAM,cAAc,cACZ,IAAI,IAAI,UAAU,QAAQ,IAAI,KAAK,CAAC,GAC1C;EAAC;EAAQ;EAAS;CAAK,CACzB;CAoBA,OAAO;EAAE;EAAY,QAlBN,aAAa,SAAiB;GAO3C,MAAM,OAAO,IAAI,IAAI,UAAU;GAC/B,IAAI,KAAK,IAAI,IAAI,GACf,KAAK,OAAO,IAAI;QAEhB,KAAK,IAAI,IAAI;GACf,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,CACxB,QAAO,MAAK,YAAY,IAAI,CAAC,CAAC,CAAC,CAC/B,KAAK;GACR,WAAW,YAAY,SAAS;EAClC,GAAG;GAAC;GAAY;GAAa;GAAY;EAAU,CAEzB;CAAE;AAC9B;;;;;;;;;;;;;;;;ACjCA,SAAgB,iBAAiB,MAAc,KAAqB;CAClE,IAAI,OAAO,GACT,OAAO;CACT,IAAI,KAAK,UAAU,KACjB,OAAO;CACT,IAAI,QAAQ,GACV,OAAO;CACT,OAAO,GAAG,KAAK,MAAM,GAAG,MAAM,CAAC,EAAE;AACnC;;;;;;AAuCA,SAAgB,WAAW,GAAqB;CAC9C,MAAM,gBAAgB,EAAE,IAAI;CAC5B,MAAM,cAAc,iBAAiB,EAAE,OAAO,IAAI,UAAU;CAC5D,MAAM,WAAW,eAAe,cAAc,KAAK,EAAE,UAAU,KAAK,IAAI,KAAK,EAAE,MAAM;CACrF,OAAO;EACL;EACA;EACA,iBAAiB;EACjB,OAAO,YAAY,EAAE,QAAQ,IAAI,EAAE,MAAM,MAAM,SAAS;CAC1D;AACF;;;;;;;;;AAUA,SAAgB,YAAY,OAAgC;CAC1D,OAAO,MAAM,QAAQ,KAAK,GAAG,MAAM,MAAM,WAAW,CAAC,CAAC,CAAC,SAAS,IAAI,IAAA,IAAe,IAAI,CAAC;AAC1F;;;;;;;;;AAUA,MAAa,cAA+B,OAAO,OAAO,CAAC,CAAC;;;;;;;;;;;;;;;AAgB5D,SAAgB,iBAAiB,OAAwB,QAAiC;CACxF,IAAI,UAAU,KAAK,MAAM,WAAW,GAClC,OAAO;CACT,MAAM,MAAc,CAAC;CACrB,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,IAAI,MAAM;EAChB,MAAM,QAAQ,IAAI,IAAA,IAAe,KAAK,WAAW,CAAC,CAAC,CAAC;EACpD,IAAI,OAAO,OAAO,QAChB;EACF,IAAI,KAAK,CAAC;EACV,QAAQ;CACV;CACA,IAAI,IAAI,WAAW,GACjB,OAAO;CACT,OAAO,IAAI,WAAW,MAAM,SAAS,QAAQ;AAC/C;;;AC7JA,MAAM,2BAA2B,cAAkD,CAAC,CAAC;AACrF,MAAM,6BAA6B,cAA0C,IAAI;;;;;;;AAQjF,SAAgB,qBAAqB,EAAE,YAAqC;CAC1E,MAAM,CAAC,OAAO,YAAY,SAA6C,CAAC,CAAC;CAIzE,MAAM,WAAW,OAA2C,CAAC,CAAC;CAC9D,SAAS,UAAU;CAOnB,sBAAsB;EACpB,KAAK,MAAM,SAAS,SAAS,SAC3B,MAAM,OAAO,iCAAiC;CAClD,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,aAAa,UAAmC;EAC9D,UAAS,SAAQ,CAAC,GAAG,MAAM,KAAK,CAAC;CACnC,GAAG,CAAC,CAAC;CAEL,MAAM,cAAc,aAAa,aAAkC;EACjE,UAAU,SAAS;GACjB,MAAM,CAAC,MAAM,GAAG,QAAQ;GACxB,IAAI,MACF,KAAK,QAAQ,QAAQ;GACvB,OAAO;EACT,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,aAAa,WAAoB;EAClD,UAAU,SAAS;GACjB,MAAM,CAAC,MAAM,GAAG,QAAQ;GACxB,IAAI,MACF,KAAK,OAAO,MAAM;GACpB,OAAO;EACT,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,aAAa,WAAoB;EACjD,UAAU,SAAS;GACjB,KAAK,MAAM,SAAS,MAAM,MAAM,OAAO,MAAM;GAC7C,OAAO,CAAC;EACV,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,WAAW,aAAa,cAAsB;EAIlD,UAAS,SAAQ,KAAK,QAAO,UAAS,MAAM,QAAQ,OAAO,SAAS,CAAC;CACvE,GAAG,CAAC,CAAC;CAML,MAAM,aAAa,OAAmC,IAAI;CAC1D,IAAI,CAAC,WAAW,SACd,WAAW,UAAU;EAAE;EAAS;EAAa;EAAY;EAAW;CAAS;CAE/E,OACE,oBAAC,2BAA2B,UAA5B;EAAqC,OAAO,WAAW;EACrD,UAAA,oBAAC,yBAAyB,UAA1B;GAAmC,OAAO;GACvC;EACgC,CAAA;CACA,CAAA;AAEzC;;AAGA,SAAgB,uBAA2D;CACzE,OAAO,WAAW,wBAAwB;AAC5C;;AAGA,SAAgB,yBAA8C;CAC5D,MAAM,MAAM,WAAW,0BAA0B;CACjD,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,mEAAmE;CACrF,OAAO;AACT;;;AChFA,MAAM,eAAe,cAA+B,CAAC,CAAC;AACtD,MAAM,kBAAkB,cAAsD,IAAI;;;;;;;AAQlF,SAAgB,gBAAgB,EAAE,YAAqC;CACrE,MAAM,CAAC,OAAO,YAAY,SAA0B,CAAC,CAAC;CACtD,MAAM,WAAW,aAAa,UAAwB;EACpD,UAAS,SAAQ,cAAc,MAAM,KAAK,CAAC;CAC7C,GAAG,CAAC,CAAC;CACL,OACE,oBAAC,aAAa,UAAd;EAAuB,OAAO;EAC5B,UAAA,oBAAC,gBAAgB,UAAjB;GAA0B,OAAO;GAC9B;EACuB,CAAA;CACL,CAAA;AAE3B;;AAGA,SAAgB,kBAAmC;CACjD,OAAO,WAAW,YAAY;AAChC;;AAGA,SAAgB,qBAAoD;CAClE,MAAM,WAAW,WAAW,eAAe;CAC3C,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,0DAA0D;CAC5E,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;ACRA,SAAgB,oBACd,SACA,UACA,eACiB;CACjB,MAAM,MAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,KAAK;GAAE,MAAM;GAAU;EAAM,CAAC;EAClC,IAAI,CAAC,SAAS,IAAI,MAAM,OAAO,IAAI,GACjC;EACF,MAAM,QAAQ,cAAc,MAAM,OAAO,SAAS,CAAC;EACnD,IAAI,MAAM,WAAW,GAAG;GACtB,IAAI,KAAK;IAAE,MAAM;IAAc,YAAY,MAAM,OAAO;GAAK,CAAC;GAC9D;EACF;EACA,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK;GAAE,MAAM;GAAQ,YAAY,MAAM,OAAO;GAAM;EAAK,CAAC;CAClE;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,iBAAiB,KAA4B;CAC3D,OAAO,IAAI,SAAS,WAAW,IAAI,MAAM,OAAO,OAAO,IAAI;AAC7D;;;;;;;;;;;AAYA,SAAgB,iBAAiB,MAAgC,YAA4B;CAC3F,OAAO,KAAK,WACV,MAAK,EAAE,SAAS,YAAY,EAAE,MAAM,OAAO,SAAS,UACtD;AACF;;;;;;;;;;;;;;;AC9CA,SAAgB,oBAAoB,MAGf;CACnB,MAAM,EAAE,UAAU,eAAe,YAAY;CAC7C,MAAM,cAAc,aACjB,SAAuC,WAAW,oBAAoB,IAAI,GAC3E,CAAC,UAAU,CACb;CAEA,MAAM,eAAe,cAAc,KAAK,QAAQ,KAAI,MAAK,EAAE,IAAI,GAAG,CAAC,KAAK,OAAO,CAAC;CAChF,MAAM,mBAAmB,SAAS;CAElC,OAAO,cACC,gBAAgB;EACpB,YAAY,KAAK;EACjB;EACA;EACA,qBAAqB;CACvB,CAAC,GACD;EAAC,KAAK;EAAY;EAAc;EAAkB;CAAW,CAC/D;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBAAoB,MAEC;CACnC,MAAM,EAAE,UAAU,eAAe,YAAY;CAC7C,MAAM,cAAc,aACjB,SAAuC,WAAW,oBAAoB,IAAI,GAC3E,CAAC,UAAU,CACb;CACA,MAAM,mBAAmB,SAAS;CAElC,OAAO,cAAc;EACnB,MAAM,MAAwC,CAAC;EAC/C,KAAK,MAAM,CAAC,YAAY,YAAY,OAAO,QAAQ,KAAK,eAAe,GACrE,IAAI,cAAc,gBAAgB;GAChC;GACA,cAAc,QAAQ,KAAI,MAAK,EAAE,IAAI;GACrC;GACA,qBAAqB;EACvB,CAAC;EAEH,OAAO;CACT,GAAG;EAAC,KAAK;EAAiB;EAAkB;CAAW,CAAC;AAC1D;;;;;;;;;;;;;AAcA,SAAgB,gBAAgB,MAKX;CACnB,MAAM,EAAE,YAAY,cAAc,kBAAkB,wBAAwB;CAC5E,MAAM,gBAAgB,mBAAmB;CACzC,MAAM,aAAa,IAAI,IAAI,YAAY;CAEvC,MAAM,oBAAyC;EAC7C,IAAI,CAAC,iBAAiB,cAAc,WAAW,GAC7C,OAAO,IAAI,IAAI,YAAY;EAC7B,MAAM,OAAO,IAAI,IAAI,aAAa;EAClC,OAAO,IAAI,IAAI,aAAa,QAAO,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;CAC7D,EAAA,CAAG;CAEH,MAAM,UAAU,aAA2B;EAMzC,MAAM,cAAc,IAAI,IAAI,iBAAiB,CAAC,CAAC;EAC/C,IAAI,YAAY,IAAI,QAAQ,GAC1B,YAAY,OAAO,QAAQ;OAE3B,YAAY,IAAI,QAAQ;EAE1B,MAAM,gBAAgB,CAAC,GAAG,WAAW,CAAC,CACnC,QAAO,MAAK,WAAW,IAAI,CAAC,CAAC,CAAC,CAC9B,KAAK;EAGR,MAAM,UAA6C,EAAE,GADrC,oBAAoB,CAAC,EAC2B;EAChE,IAAI,cAAc,WAAW,GAI3B,OAAO,QAAQ;OAGf,QAAQ,cAAc;EAMxB,MAAM,OACF,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,IAAI,KAAA,IAAY;EACpD,oBAAoB,IAAI;CAC1B;CAEA,OAAO;EAAE;EAAY;CAAO;AAC9B;;;;;;;;;;;;;;;;;;;;;;;AC3KA,MAAM,iCAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;;;;;;;;;;;;;;AAezE,SAAgB,uBAAuB,SAA6B;CAClE,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,OAAO;CAC1B,QACM;EACJ,OAAO;CACT;CACA,MAAM,WAAW,OAAO,aAAa,IAAI,cAAc;CACvD,IAAI,CAAC,UACH,OAAO;CACT,IAAI;EACF,OAAO,IAAI,IAAI,QAAQ;CACzB,QACM;EACJ,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,uBAAuB,QAAgB,SAA0B;CAC/E,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,2CAA2C;CAI7D,IAAI,2BAA2B,KAAK,OAAO,GACzC,OAAO;CAET,MAAM,WAAW,UAAU,uBAAuB,OAAO,IAAI;CAC7D,IAAI,CAAC,UACH,MAAM,IAAI,MACR,oHACF;CAQF,IAAI,QAAQ,WAAW,GAAG,GAAG;EAC3B,MAAM,SAAS,IAAI,IAAI,SAAS,QAAQ;EACxC,IAAI,CAAC,OAAO,aAAa,IAAI,MAAM,KAAK,CAAC,OAAO,aAAa,IAAI,OAAO,GACtE,MAAM,IAAI,MAAM,0FAAyF;EAC3G,OAAO,OAAO,SAAS;CACzB;CAEA,MAAM,SAAS,IAAI,IAAI,SAAS,SAAS,CAAC;CAC1C,MAAM,QAAQ,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;CAQ3D,IAAI,CADa,cAAc,KAAK,KACxB,KAAK,MAAM,SAAS,GAAG,GAAG;EACpC,MAAM,SAAS,IAAI,gBAAgB,KAAK;EACxC,IAAI,CAAC,OAAO,IAAI,MAAM,KAAK,CAAC,OAAO,IAAI,OAAO,GAC5C,MAAM,IAAI,MAAM,sGAAoG;EACtH,KAAK,MAAM,CAAC,KAAK,UAAU,QACzB,OAAO,aAAa,IAAI,KAAK,KAAK;EACpC,OAAO,OAAO,SAAS;CACzB;CAMA,IAAI,KAAK,KAAK,KAAK,GACjB,MAAM,IAAI,MAAM,kHAAmH;CACrI,OAAO,aAAa,IAAI,QAAQ,KAAK;CACrC,MAAM,QAAQ,IAAI,IAAI,OAAQ,CAAC,CAAC,aAAa,IAAI,OAAO;CACxD,IAAI,OACF,OAAO,aAAa,IAAI,SAAS,KAAK;CACxC,OAAO,OAAO,SAAS;AACzB;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,mBACpB,QACA,UAA0E,CAAC,GACxC;CACnC,MAAM,UAAU,uBAAuB,QAAQ,QAAQ,OAAO;CAE9D,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,OAAO;CACvB,QACM;EACJ,MAAM,IAAI,MAAM,yEAA0E;CAC5F;CAIA,MAAM,OAAO,IAAI,SAAS,QAAQ,YAAY,EAAE;CAChD,IAAI,CAAC,eAAe,IAAI,IAAI,GAC1B,MAAM,IAAI,MACR,yDAAyD,IAAI,SAAS,8DACxE;CAKF,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,KAAK,IAAI,gBAAgB;CAC/B,MAAM,QAAQ,iBAAiB,GAAG,MAAM,GAAG,SAAS;CACpD,QAAQ,QAAQ,iBAAiB,eAAe,GAAG,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;CAE1E,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC9D,SACO,KAAK;EACV,IAAK,IAAc,SAAS,cAC1B,MAAM,IAAI,MAAM,sEAAsE;EACxF,MAAM,IAAI,MAAM,8CAA+C,IAAc,SAAS;CACxF,UACQ;EACN,aAAa,KAAK;CACpB;CAEA,MAAM,WAAW,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;CACrD,OAAO;EAAE,QAAQ,SAAS;EAAQ,SAAS,eAAe,QAAQ;CAAE;AACtE;;;;;;AAOA,SAAS,eAAe,MAAkC;CACxD,IAAI,CAAC,MACH,OAAO,KAAA;CACT,MAAM,KAAK,6BAA6B,KAAK,IAAI,CAAC,GAAG;CACrD,IAAI,IACF,OAAO,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,KAAA;CACjC,MAAM,IAAI,2BAA2B,KAAK,IAAI,CAAC,GAAG;CAClD,IAAI,GACF,OAAO,UAAU,CAAC,CAAC,CAAC,KAAK,KAAK,KAAA;AAElC;AAEA,SAAS,UAAU,GAAmB;CACpC,OAAO,EAAE,QAAQ,YAAY,EAAE,CAAC,CAAC,QAAQ,UAAU,GAAG,CAAC,CAAC,QAAQ,SAAS,GAAG,CAAC,CAAC,QAAQ,SAAS,GAAG,CAAC,CAAC,QAAQ,WAAW,IAAG,CAAC,CAAC,QAAQ,UAAU,GAAI;AACpJ;;;;AC3NA,SAAS,QAAQ,QAAgB,SAAqC;CACpE,OAAO,GAAG,WAAW,GAAG,QAAQ;AAClC;;;;;;;;AASA,SAAgB,oBAAoB,QAAuC;CACzE,MAAM,OAAO,IAAI,IAAI,OAAO,SAAQ,MAAM,EAAE,SAAS,iBAAiB,EAAE,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAE,CAAC;CACtH,OAAO,OAAO,MAAK,MAAK,EAAE,SAAS,UAAU,EAAE,WAAW,KAAA,KAAa,EAAE,cAAc,KAAA,KAAa,CAAC,KAAK,IAAI,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC7I;;;;;;;;;;AAWA,SAAgB,sBAA2C,QAAa,MAAmG;CACzK,MAAM,UAAU,IAAI,IAAI,KAAK,SAAQ,SAAS,KAAK,SAAS,SAAS,CAAC,CAAC,QAAQ,KAAK,IAAI,KAAK,OAAO,GAAG,IAAI,CAAU,IAAI,CAAC,CAAE,CAAC;CAC7H,IAAI,QAAQ,SAAS,GACnB,OAAO;CACT,IAAI,UAAU;CACd,MAAM,MAAM,OAAO,KAAK,UAAU;EAChC,MAAM,OAAO,MAAM,SAAS,UAAU,MAAM,SAAS,QAAQ,IAAI,QAAQ,MAAM,QAAQ,MAAM,OAAO,CAAC,IAAI,KAAA;EACzG,MAAM,YAAY,QAAQ,MAAM,cAAc,KAAA,IAAY,KAAK,YAAY,KAAA;EAC3E,MAAM,WAAW,MAAM,aAAa,QAAQ,MAAM,aAAa;EAC/D,IAAI,cAAc,KAAA,KAAa,CAAC,UAC9B,OAAO;EACT,UAAU;EACV,OAAO;GAAE,GAAG;GAAO,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;GAAI,GAAI,WAAW,EAAE,UAAU,KAAc,IAAI,CAAC;EAAG;CACzH,CAAC;CACD,OAAO,UAAU,MAAM;AACzB;;;AC0CA,MAAM,uBAAuB,cAA0C,CAAC,CAAC;AACzE,MAAM,yBAAyB,cAAsC,IAAI;AAEzE,IAAI,oBAAoB;AACxB,SAAS,iBAAyB;CAIhC,qBAAqB;CACrB,OAAO,YAAY;AACrB;;;;;;;AAQA,SAAgB,iBAAiB,EAAE,YAAqC;CACtE,MAAM,CAAC,OAAO,YAAY,SAAqC,CAAC,CAAC;CAIjE,MAAM,WAAW,OAAmC,CAAC,CAAC;CACtD,SAAS,UAAU;CAMnB,sBAAsB;EACpB,KAAK,MAAM,KAAK,SAAS,SACvB,EAAE,QAAQ,MAAM;CACpB,GAAG,CAAC,CAAC;CAKL,MAAM,kBAAkB,aACrB,MAAM,OAAO,YAAY,kBAAkB,IAAI,SAA2B,YAAY;EACrF,UAAS,SAAQ,CACf,GAAG,MACH;GACE,IAAI,eAAe;GACnB;GACA;GACA;GACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACnC,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;EAC3C,CACF,CAAC;CACH,CAAC,GACD,CAAC,CACH;CAEA,MAAM,cAAc,aAAa,aAA+B;EAC9D,UAAU,SAAS;GACjB,MAAM,CAAC,MAAM,GAAG,QAAQ;GACxB,IAAI,MACF,KAAK,QAAQ,QAAQ;GACvB,OAAO;EACT,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,kBAAkB;EAChC,UAAU,SAAS;GACjB,KAAK,MAAM,KAAK,MAAM,EAAE,QAAQ,MAAM;GACtC,OAAO,CAAC;EACV,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,MAAM,oBAAoB,aAAa,kBAA0B;EAI/D,UAAS,SAAQ,KAAK,QAAO,MAAK,EAAE,kBAAkB,aAAa,CAAC;CACtE,GAAG,CAAC,CAAC;CAML,MAAM,aAAa,OAA+B,IAAI;CACtD,IAAI,CAAC,WAAW,SACd,WAAW,UAAU;EAAE;EAAiB;EAAa;EAAS;CAAkB;CAElF,OACE,oBAAC,uBAAuB,UAAxB;EAAiC,OAAO,WAAW;EACjD,UAAA,oBAAC,qBAAqB,UAAtB;GAA+B,OAAO;GACnC;EAC4B,CAAA;CACA,CAAA;AAErC;;AAGA,SAAgB,mBAA+C;CAC7D,OAAO,WAAW,oBAAoB;AACxC;;AAGA,SAAgB,qBAAsC;CACpD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,2DAA2D;CAC7E,OAAO;AACT;;;;;;;;;;;AC1LA,MAAM,mBAAmB;;;;;;;;;;AAWzB,MAAa,0BAA0B;AAEvC,MAAM,eAAsB;AAoB5B,SAAS,YAAY,OAAc,OAA4B;CAC7D,OAAO;EAAE,UAAU;EAAI,UAAU;EAAI;EAAO;EAAO,aAAa;CAAE;AACpE;AAEA,SAAS,YAAY,MAAqB,QAAoC;CAC5E,IAAI,SAAS;CACb,IAAI,OAAO,UACT,SAAS,oBAAoB,QAAQ,OAAO,UAAU,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;CACjG,IAAI,OAAO,UACT,SAAS,oBAAoB,QAAQ,OAAO,UAAU,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;CACjG,OAAO;AACT;;;;;;AAoHA,SAAS,iBACP,SACA,QACA,MACuD;CACvD,MAAM,WAA0B,CAAC;CACjC,IAAI,kBAAkB;CACtB,KAAK,MAAM,UAAU,QAAQ,OAAO,GAAG;EACrC,MAAM,WAAW,OAAO;EACxB,OAAO,WAAW;EAElB,IAAI,QAAQ;EACZ,IAAI,OAAO,SAAS,SAAS,GAAG;GAC9B,IAAI,QAAQ;IACV,MAAM,EAAE,MAAM,UAAU,mBACtB,OAAO,SAAS,QAChB,OAAO,aACP,IACF;IACA,OAAO,cAAc;IACrB,IAAI,OAAO,GAAG;KACZ,MAAM,SAAS,UAAU,OAAO,UAAU,IAAI;KAC9C,QAAQ,OAAO;KACf,OAAO,WAAW,OAAO;KACzB,IAAI,OAAO,SAAS,WAAW,GAC7B,OAAO,cAAc;IACzB;GACF,OACK;IACH,QAAQ,OAAO;IACf,OAAO,WAAW;IAClB,OAAO,cAAc;GACvB;EACF;EAEA,IAAI,SAAS,UACX,SAAS,KAAK;GACZ,UAAU;GACV;GACA,OAAO,OAAO;GACd,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,aAAa;EACf,CAAC;EAEH,IAAI,OAAO,SAAS,SAAS,GAC3B,kBAAkB;CACtB;CACA,OAAO;EAAE;EAAU;CAAgB;AACrC;;AAGA,SAAS,WAAW,SAAkC,iBAAkC;CACtF,IAAI,OAAO,oBAAoB;CAC/B,IAAI,MACG;OAAA,MAAM,UAAU,QAAQ,OAAO,GAClC,IAAI,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,GAAG;GAC5D,OAAO;GACP;EACF;;CAGJ,OAAO;AACT;;AAKA,SAAS,eACP,SACA,kBACA,iBACA,WACM;CAQN,IAAI,eAAe,iBAAiB;CACpC,MAAM,SAAS;EACb;EACA,OAAO,KAAA;CACT;CACA,MAAM,YAA2C,iBAAiB;EAChE,IAAI,iBAAiB,YAAY,cAAc;GAC7C,eAAe,iBAAiB;GAChC,OAAO,QAAQ,IAAI;GACnB;EACF;EACA,gBAAgB,UAAU,gBAAgB,QAAQ,QAAO,MAAK,MAAM,MAAM;EAC1E,QAAQ;CACV,GAAG,SAAS;CACZ,OAAO,QAAQ,IAAI;CACnB,gBAAgB,QAAQ,KAAK,MAAM;AACrC;;;;;;;AAQA,SAAgB,gBACd,WACA,SACc;CACd,MAAM,aAAa,uBAAgC,IAAI,IAAI,CAAC;CAC5D,MAAM,YAAY,OAA8C,IAAI;CAMpE,MAAM,gBAAgB,OAAe,CAAC;CAGtC,MAAM,eAAe,OAAoC,SAAS,SAAS;CAC3E,aAAa,UAAU,SAAS;;CAEhC,MAAM,eAAe,OAAkC,SAAS,mBAAmB;CACnF,aAAa,UAAU,SAAS;CAChC,MAAM,kBAAkB,OAAO,SAAS,sBAAsB;CAC9D,gBAAgB,UAAU,SAAS;CACnC,MAAM,eAAe,OAAO,SAAS,mBAAmB;CACxD,aAAa,UAAU,SAAS;CAChC,MAAM,qBAAqB,OAAO,SAAS,iBAAiB;CAC5D,mBAAmB,UAAU,SAAS;;CAEtC,MAAM,kBAAkB,OAAO,gBAAgB;CAC/C,MAAM,eAAe,aAAa,WAAyC;EACzE,MAAM,MAAM,gBAAgB,UAAU,gBAAgB,QAAQ,IAAI,aAAa;EAE/E,OAAO,QAAQ,OAAO,SAAS,kBAAkB,QAAQ,OAAO,KAAA,GAAW,KAAA,GAAW,aAAa,UAAU,CAAC;CAChH,GAAG,CAAC,CAAC;;;;;;;;;;;;;CAcL,MAAM,qBAAqB,OAAqD,CAAC,CAAC;;;;;;CAOlF,MAAM,kBAAkB,OAAsB,CAAC,CAAC;;;;;;CAOhD,MAAM,mBAAmB,OAAO,CAAC;CAEjC,MAAM,sBAAsB,kBAAkB;EAC5C,IAAI,gBAAgB,QAAQ,WAAW,GACrC;EACF,MAAM,UAAU,gBAAgB;EAChC,gBAAgB,UAAU,CAAC;EAC3B,KAAK,MAAM,UAAU,SAAS;GAC5B,aAAa,OAAO,KAAK;GACzB,OAAO,QAAQ;EACjB;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,kBAAkB;EACnC,IAAI,UAAU,SAAS;GACrB,cAAc,UAAU,OAAO;GAC/B,UAAU,UAAU;EACtB;EAIA,cAAc,UAAU;CAC1B,GAAG,CAAC,CAAC;;;;;;;;;CAUL,MAAM,qBAAqB,kBAA2B;EACpD,KAAK,MAAM,UAAU,WAAW,QAAQ,OAAO,GAC7C,IAAI,OAAO,SAAS,SAAS,GAC3B,OAAO;EAEX,OAAO;CACT,GAAG,CAAC,CAAC;;;;;;;CAQL,MAAM,WAAW,aAAa,YAAuD;EACnF,WAAW;EACX,MAAM,UAAU,MAAM,KAAK,WAAW,QAAQ,OAAO,CAAC;EACtD,WAAW,QAAQ,MAAM;EACzB,MAAM,SAAS,mBAAmB;EAClC,mBAAmB,UAAU,CAAC;EAE9B,IAAI,CADc,QAAQ,MAAK,MAAK,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,SAAS,CACpE,KAAK,CAAC,WAAW,OAAO,WAAW,GAAG;GACjD,oBAAoB;GACpB;EACF;EACA,WAAW,SAAS;GAClB,IAAI,SAAS;GACb,KAAK,MAAM,UAAU,SACnB,SAAS,YAAY,QAAQ,MAAM;GACrC,KAAK,MAAM,KAAK,QACd,SAAS,EAAE,MAAM;GACnB,IAAI,SACF,SAAS,QAAQ,MAAM;GAGzB,OAAO,aAAa,MAAM;EAC5B,CAAC;EACD,oBAAoB;CACtB,GAAG;EAAC;EAAc;EAAqB;EAAW;CAAU,CAAC;;;;;;;;;;;;CAa7D,MAAM,OAAO,kBAAkB;EAC7B,MAAM,SAAS,aAAa,UAAU,KAAK;EAC3C,MAAM,UAAU,WAAW;EAM3B,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,WAAW,gBAAgB;EACjC,MAAM,OAAO,cAAc,YAAY,IACnC,WACA,KAAK,IAAI,WAAW,GAAG,KAAK,IAAI,GAAG,MAAM,cAAc,OAAO,CAAC;EACnE,cAAc,UAAU;EAExB,MAAM,EAAE,UAAU,oBAAoB,iBAAiB,SAAS,QAAQ,IAAI;EAO5E,MAAM,SADc,CAAC,mBAAmB,mBAAmB,QAAQ,SAAS,IAExE,mBAAmB,QAAQ,OAAO,GAAG,mBAAmB,QAAQ,MAAM,IACtE,CAAC;EAEL,IAAI,SAAS,SAAS,KAAK,OAAO,SAAS,GAAG;GAC5C,iBAAiB,WAAW;GAC5B,WAAW,SAAS;IAClB,IAAI,OAAO;IACX,KAAK,MAAM,WAAW,UACpB,OAAO,YAAY,MAAM,OAAO;IAClC,KAAK,MAAM,KAAK,QACd,OAAO,EAAE,IAAI;IAIf,OAAO,aAAa,IAAI;GAC1B,CAAC;EACH;EAEA,IAAI,CAAC,iBAAiB;GACpB,WAAW;GAGX,oBAAoB;EACtB;CACF,GAAG;EAAC;EAAc;EAAqB;EAAW;CAAU,CAAC;CAE7D,MAAM,eAAe,kBAAkB;EACrC,IAAI,UAAU,SACZ;EACF,MAAM,YAAY,mBAAmB,UAAU;EAC/C,gBAAgB,UAAU,cAAc,KAAA,KAAa,OAAO,SAAS,SAAS,KAAK,YAAY,IAAI,YAAY;EAC/G,UAAU,UAAU,YAAY,MAAM,gBAAgB,OAAO;CAC/D,GAAG,CAAC,IAAI,CAAC;;;;;;;;;;CAWT,MAAM,eAAe,aAAa,YAAsD;EAEtF,KADe,aAAa,UAAU,KAAK,SAC7B,mBAAmB,GAAG;GAClC,mBAAmB,QAAQ,KAAK,OAAO;GACvC,aAAa;GACb;EACF;EACA,SAAS,OAAO;CAClB,GAAG;EAAC;EAAU;EAAc;CAAkB,CAAC;CAE/C,MAAM,QAAQ,kBAAkB;EAM9B,KADe,aAAa,UAAU,KAAK,SAC7B,mBAAmB,GAAG;GAClC,aAAa;GACb;EACF;EACA,SAAS;CACX,GAAG;EAAC;EAAU;EAAc;CAAkB,CAAC;CAE/C,MAAM,iBAAiB,aACpB,WAAqD,aAAa,MAAM,GACzE,CAAC,YAAY,CACf;CAEA,MAAM,kBAAkB,aACrB,QAAqB,cAAa,WAAU,CAAC,GAAG,QAAQ,GAAG,CAAC,GAC7D,CAAC,YAAY,CACf;CAEA,MAAM,mBAAmB,aACvB,MACA,OACA,WACG;EACH,IAAI,CAAC,OACH;EACF,MAAM,QAAe,QAAQ,WAAW;EACxC,MAAM,QAAQ,QAAQ,SAAS;EAC/B,IAAI,SAAS,WAAW,QAAQ,IAAI,KAAK;EACzC,IAAI,CAAC,QAAQ;GACX,SAAS,YAAY,OAAO,KAAK;GACjC,WAAW,QAAQ,IAAI,OAAO,MAAM;EACtC;EACA,OAAO,SAAS;EAKhB,IAAI,QAAQ,QACV,OAAO,SAAS,OAAO;EACzB,aAAa;CACf,GAAG,CAAC,YAAY,CAAC;CAEjB,MAAM,QAAQ,kBAAkB;EAC9B,WAAW;EACX,WAAW,QAAQ,MAAM;EACzB,mBAAmB,UAAU,CAAC;EAG9B,oBAAoB;CACtB,GAAG,CAAC,qBAAqB,UAAU,CAAC;CAEpC,MAAM,cAAc,aAAa,YAAoB,4BAA2C;EAC9F,IAAI,WAAW,WAAW,SAAS,mBAAmB,QAAQ,MAAM,GAClE,OAAO,QAAQ,QAAQ;EACzB,OAAO,IAAI,SAAQ,YAAW,eAAe,SAAS,kBAAkB,iBAAiB,SAAS,CAAC;CACrG,GAAG,CAAC,CAAC;CAML,gBAAgB,OAAO,CAAC,KAAK,CAAC;CAO9B,OAAO,eACE;EAAE;EAAkB;EAAiB;EAAgB;EAAO;EAAO;CAAY,IACtF;EAAC;EAAkB;EAAiB;EAAgB;EAAO;EAAO;CAAW,CAC/E;AACF;;;ACnjBA,MAAM,eAAe,cAAqB,aAAa;;;;;;AAOvD,SAAgB,cAAc,EAAE,OAAO,YAAmD;CACxF,OAAO,oBAAC,aAAa,UAAd;EAAuB,OAAO;EAAQ;CAAgC,CAAA;AAC/E;;AAGA,SAAgB,WAAkB;CAChC,OAAO,WAAW,YAAY;AAChC;;AAGA,SAAgB,YAAyB;CACvC,OAAO,WAAW,YAAY,CAAC,CAAC;AAClC;;AAGA,SAAgB,iBAA8B;CAC5C,OAAO,WAAW,YAAY,CAAC,CAAC;AAClC;;AAGA,SAAgB,cAA6B;CAC3C,OAAO,WAAW,YAAY,CAAC,CAAC;AAClC;;AAGA,SAAgB,kBAAgC;CAC9C,OAAO,WAAW,YAAY,CAAC,CAAC;AAClC;;;;;;;;;;;;;;;;;;;;;;;ACWA,SAAgB,mBAAmB,OAIjC;CACA,MAAM,2BAAW,IAAI,IAAoB;CACzC,IAAI;CACJ,MAAM,OAAO,WAAmD;EAC9D,IAAI,CAAC,QACH,OAAO,KAAA;EACT,aAAa;EACb,IAAI,SAAS,IAAI,MAAM,GACrB,OAAO,KAAA;EACT,MAAM,KAAK,eAAe;EAC1B,SAAS,IAAI,QAAQ,EAAE;EACvB,OAAO;CACT;CACA,MAAM,MAAgC,CAAC;CACvC,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,SAChB,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,MAAM,CAAC,CAAC;MAEjC,IAAI,KAAK,KAAK,OAAO,KAAI,MAAK,IAAI,EAAE,MAAM,CAAC,CAAC;CAEhD,OAAO;EAAE;EAAU;EAAK;CAAW;AACrC"}