kanna-code 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  2. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  3. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  4. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  5. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  6. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  7. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  8. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  9. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  10. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  11. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  12. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  13. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  14. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  15. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  16. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  17. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  18. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  19. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  20. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  21. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  22. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  23. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  24. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  25. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  26. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  27. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  28. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  29. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  30. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  31. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  32. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  33. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  34. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  35. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  36. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  37. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  38. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  39. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  40. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  41. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  42. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  43. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  44. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  45. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  46. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  47. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  48. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  49. package/dist/client/assets/css-DPfMkruS.js +1 -0
  50. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  51. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  52. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  53. package/dist/client/assets/d-85-TOEBH.js +1 -0
  54. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  55. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  56. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  57. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  58. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  59. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  60. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  61. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  62. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  63. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  64. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  65. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  66. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  67. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  68. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  69. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  70. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  71. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  72. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  73. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  74. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  75. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  76. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  77. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  78. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  79. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  80. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  81. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  82. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  83. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  84. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  85. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  86. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  87. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  88. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  89. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  90. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  91. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  92. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  93. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  94. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  95. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  96. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  97. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  98. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  99. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  100. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  101. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  102. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  103. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  104. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  105. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  106. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  107. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  108. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  109. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  110. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  111. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  112. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  113. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  114. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  115. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  116. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  117. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  118. package/dist/client/assets/html-GMplVEZG.js +1 -0
  119. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  120. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  121. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  122. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  123. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  124. package/dist/client/assets/imba-DGztddWO.js +1 -0
  125. package/dist/client/assets/index-B-8fHyaU.css +32 -0
  126. package/dist/client/assets/index-DrUC3AGz.js +2502 -0
  127. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  128. package/dist/client/assets/java-CylS5w8V.js +1 -0
  129. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  130. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  131. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  132. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  133. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  134. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  135. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  136. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  137. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  138. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  139. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  140. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  141. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  142. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  143. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  144. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  145. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  146. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  147. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  148. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  149. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  150. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  151. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  152. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  153. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  154. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  155. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  156. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  157. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  158. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  159. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  160. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  161. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  162. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  163. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  164. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  165. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  166. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  167. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  168. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  169. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  170. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  171. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  172. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  173. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  174. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  175. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  176. package/dist/client/assets/move-IF9eRakj.js +1 -0
  177. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  178. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  179. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  180. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  181. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  182. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  183. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  184. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  185. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  186. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  187. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  188. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  189. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  190. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  191. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  192. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  193. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  194. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  195. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  196. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  197. package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
  198. package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
  199. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  200. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  201. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  202. package/dist/client/assets/po-BTJTHyun.js +1 -0
  203. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  204. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  205. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  206. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  207. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  208. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  209. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  210. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  211. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  212. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  213. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  214. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  215. package/dist/client/assets/qml-3beO22l8.js +1 -0
  216. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  217. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  218. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  219. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  220. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  221. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  222. package/dist/client/assets/red-bN70gL4F.js +1 -0
  223. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  224. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  225. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  226. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  227. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  228. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  229. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  230. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  231. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  232. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  233. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  234. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  235. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  236. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  237. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  238. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  239. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  240. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  241. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  242. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  243. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  244. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  245. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  246. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  247. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  248. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  249. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  250. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  251. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  252. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  253. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  254. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  255. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  256. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  257. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  258. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  259. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  260. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  261. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  262. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  263. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  264. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  265. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  266. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  267. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  268. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  269. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  270. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  271. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  272. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  273. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  274. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  275. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  276. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  277. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  278. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  279. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  280. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  281. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  282. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  283. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  284. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  285. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  286. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  287. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  288. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  289. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  290. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  291. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  292. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  293. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  294. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  295. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  296. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  297. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  298. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  299. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  300. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  301. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  302. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  303. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  304. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  305. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  306. package/dist/client/index.html +2 -2
  307. package/package.json +2 -1
  308. package/src/server/agent.test.ts +589 -1
  309. package/src/server/agent.ts +459 -22
  310. package/src/server/codex-app-server-protocol.ts +28 -0
  311. package/src/server/codex-app-server.test.ts +88 -0
  312. package/src/server/codex-app-server.ts +71 -1
  313. package/src/server/diff-store.test.ts +126 -0
  314. package/src/server/diff-store.ts +365 -0
  315. package/src/server/generate-commit-message.test.ts +59 -0
  316. package/src/server/generate-commit-message.ts +121 -0
  317. package/src/server/read-models.test.ts +1 -1
  318. package/src/server/read-models.ts +6 -1
  319. package/src/server/server.ts +60 -1
  320. package/src/server/terminal-manager.test.ts +37 -4
  321. package/src/server/uploads.ts +4 -0
  322. package/src/server/ws-router.test.ts +5 -5
  323. package/src/server/ws-router.ts +125 -7
  324. package/src/shared/protocol.ts +4 -0
  325. package/src/shared/types.ts +51 -0
  326. package/dist/client/assets/index-C952_xJD.css +0 -32
  327. package/dist/client/assets/index-lgfU0zw2.js +0 -606
@@ -1,7 +1,8 @@
1
- import { query, type CanUseTool, type PermissionResult, type Query } from "@anthropic-ai/claude-agent-sdk"
1
+ import { query, type CanUseTool, type PermissionResult, type Query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"
2
2
  import type {
3
3
  AgentProvider,
4
4
  ChatAttachment,
5
+ ContextWindowUsageSnapshot,
5
6
  NormalizedToolCall,
6
7
  PendingToolSnapshot,
7
8
  KannaStatus,
@@ -64,11 +65,41 @@ interface ActiveTurn {
64
65
  cancelRecorded: boolean
65
66
  }
66
67
 
68
+ interface ClaudeSessionHandle {
69
+ provider: "claude"
70
+ stream: AsyncIterable<HarnessEvent>
71
+ getAccountInfo?: () => Promise<any>
72
+ interrupt: () => Promise<void>
73
+ close: () => void
74
+ sendPrompt: (content: string) => Promise<void>
75
+ setModel: (model: string) => Promise<void>
76
+ setPermissionMode: (planMode: boolean) => Promise<void>
77
+ }
78
+
79
+ interface ClaudeSessionState {
80
+ chatId: string
81
+ session: ClaudeSessionHandle
82
+ localPath: string
83
+ model: string
84
+ effort?: string
85
+ planMode: boolean
86
+ sessionToken: string | null
87
+ accountInfoLoaded: boolean
88
+ }
89
+
67
90
  interface AgentCoordinatorArgs {
68
91
  store: EventStore
69
92
  onStateChange: () => void
70
93
  codexManager?: CodexAppServerManager
71
94
  generateTitle?: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
95
+ startClaudeSession?: (args: {
96
+ localPath: string
97
+ model: string
98
+ effort?: string
99
+ planMode: boolean
100
+ sessionToken: string | null
101
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
102
+ }) => Promise<ClaudeSessionHandle>
72
103
  }
73
104
 
74
105
  function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
@@ -91,6 +122,14 @@ function stringFromUnknown(value: unknown) {
91
122
  }
92
123
  }
93
124
 
125
+ function asRecord(value: unknown): Record<string, unknown> | null {
126
+ return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null
127
+ }
128
+
129
+ function asNumber(value: unknown): number | undefined {
130
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined
131
+ }
132
+
94
133
  function escapeXmlAttribute(value: string) {
95
134
  return value
96
135
  .replaceAll("&", "&amp;")
@@ -141,6 +180,72 @@ function discardedToolResult(
141
180
  }
142
181
  }
143
182
 
183
+ export function normalizeClaudeUsageSnapshot(
184
+ value: unknown,
185
+ maxTokens?: number,
186
+ ): ContextWindowUsageSnapshot | null {
187
+ const usage = asRecord(value)
188
+ if (!usage) return null
189
+
190
+ const directInputTokens = asNumber(usage.input_tokens) ?? asNumber(usage.inputTokens) ?? 0
191
+ const cacheCreationInputTokens =
192
+ asNumber(usage.cache_creation_input_tokens) ?? asNumber(usage.cacheCreationInputTokens) ?? 0
193
+ const cacheReadInputTokens =
194
+ asNumber(usage.cache_read_input_tokens) ?? asNumber(usage.cacheReadInputTokens) ?? 0
195
+ const outputTokens = asNumber(usage.output_tokens) ?? asNumber(usage.outputTokens) ?? 0
196
+ const reasoningOutputTokens =
197
+ asNumber(usage.reasoning_output_tokens) ?? asNumber(usage.reasoningOutputTokens)
198
+ const toolUses = asNumber(usage.tool_uses) ?? asNumber(usage.toolUses)
199
+ const durationMs = asNumber(usage.duration_ms) ?? asNumber(usage.durationMs)
200
+
201
+ const inputTokens = directInputTokens + cacheCreationInputTokens + cacheReadInputTokens
202
+ const usedTokens = inputTokens + outputTokens
203
+ if (usedTokens <= 0) {
204
+ return null
205
+ }
206
+
207
+ return {
208
+ usedTokens,
209
+ inputTokens,
210
+ ...(cacheReadInputTokens > 0 ? { cachedInputTokens: cacheReadInputTokens } : {}),
211
+ ...(outputTokens > 0 ? { outputTokens } : {}),
212
+ ...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
213
+ lastUsedTokens: usedTokens,
214
+ lastInputTokens: inputTokens,
215
+ ...(cacheReadInputTokens > 0 ? { lastCachedInputTokens: cacheReadInputTokens } : {}),
216
+ ...(outputTokens > 0 ? { lastOutputTokens: outputTokens } : {}),
217
+ ...(reasoningOutputTokens !== undefined ? { lastReasoningOutputTokens: reasoningOutputTokens } : {}),
218
+ ...(toolUses !== undefined ? { toolUses } : {}),
219
+ ...(durationMs !== undefined ? { durationMs } : {}),
220
+ ...(typeof maxTokens === "number" && maxTokens > 0 ? { maxTokens } : {}),
221
+ compactsAutomatically: false,
222
+ }
223
+ }
224
+
225
+ export function maxClaudeContextWindowFromModelUsage(modelUsage: unknown): number | undefined {
226
+ const record = asRecord(modelUsage)
227
+ if (!record) return undefined
228
+
229
+ let maxContextWindow: number | undefined
230
+ for (const value of Object.values(record)) {
231
+ const usage = asRecord(value)
232
+ const contextWindow = asNumber(usage?.contextWindow) ?? asNumber(usage?.context_window)
233
+ if (contextWindow === undefined) continue
234
+ maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow)
235
+ }
236
+ return maxContextWindow
237
+ }
238
+
239
+ function getClaudeAssistantMessageUsageId(message: any): string | null {
240
+ if (typeof message?.message?.id === "string" && message.message.id) {
241
+ return message.message.id
242
+ }
243
+ if (typeof message?.uuid === "string" && message.uuid) {
244
+ return message.uuid
245
+ }
246
+ return null
247
+ }
248
+
144
249
  export function normalizeClaudeStreamMessage(message: any): TranscriptEntry[] {
145
250
  const debugRaw = JSON.stringify(message)
146
251
  const messageId = typeof message.uuid === "string" ? message.uuid : undefined
@@ -258,26 +363,129 @@ export function normalizeClaudeStreamMessage(message: any): TranscriptEntry[] {
258
363
  }
259
364
 
260
365
  async function* createClaudeHarnessStream(q: Query): AsyncGenerator<HarnessEvent> {
366
+ let seenAssistantUsageIds = new Set<string>()
367
+ let latestUsageSnapshot: ContextWindowUsageSnapshot | null = null
368
+ let lastKnownContextWindow: number | undefined
369
+
261
370
  for await (const sdkMessage of q as AsyncIterable<any>) {
262
371
  const sessionToken = typeof sdkMessage.session_id === "string" ? sdkMessage.session_id : null
263
372
  if (sessionToken) {
264
373
  yield { type: "session_token", sessionToken }
265
374
  }
375
+
376
+ if (sdkMessage?.type === "assistant") {
377
+ const usageId = getClaudeAssistantMessageUsageId(sdkMessage)
378
+ const usageSnapshot = normalizeClaudeUsageSnapshot(sdkMessage.usage, lastKnownContextWindow)
379
+ if (usageId && usageSnapshot && !seenAssistantUsageIds.has(usageId)) {
380
+ seenAssistantUsageIds.add(usageId)
381
+ latestUsageSnapshot = usageSnapshot
382
+ yield {
383
+ type: "transcript",
384
+ entry: timestamped({
385
+ kind: "context_window_updated",
386
+ usage: usageSnapshot,
387
+ }),
388
+ }
389
+ }
390
+ }
391
+
392
+ if (sdkMessage?.type === "result") {
393
+ const resultContextWindow = maxClaudeContextWindowFromModelUsage(sdkMessage.modelUsage)
394
+ if (resultContextWindow !== undefined) {
395
+ lastKnownContextWindow = resultContextWindow
396
+ }
397
+
398
+ const accumulatedUsage = normalizeClaudeUsageSnapshot(
399
+ sdkMessage.usage,
400
+ resultContextWindow ?? lastKnownContextWindow,
401
+ )
402
+ const finalUsage = latestUsageSnapshot
403
+ ? {
404
+ ...latestUsageSnapshot,
405
+ ...(typeof (resultContextWindow ?? lastKnownContextWindow) === "number"
406
+ ? { maxTokens: resultContextWindow ?? lastKnownContextWindow }
407
+ : {}),
408
+ ...(accumulatedUsage && accumulatedUsage.usedTokens > latestUsageSnapshot.usedTokens
409
+ ? { totalProcessedTokens: accumulatedUsage.usedTokens }
410
+ : {}),
411
+ }
412
+ : accumulatedUsage
413
+
414
+ if (finalUsage) {
415
+ yield {
416
+ type: "transcript",
417
+ entry: timestamped({
418
+ kind: "context_window_updated",
419
+ usage: finalUsage,
420
+ }),
421
+ }
422
+ }
423
+
424
+ seenAssistantUsageIds = new Set<string>()
425
+ latestUsageSnapshot = null
426
+ }
427
+
266
428
  for (const entry of normalizeClaudeStreamMessage(sdkMessage)) {
267
429
  yield { type: "transcript", entry }
268
430
  }
269
431
  }
270
432
  }
271
433
 
272
- async function startClaudeTurn(args: {
273
- content: string
434
+ class AsyncMessageQueue<T> implements AsyncIterable<T> {
435
+ private readonly values: T[] = []
436
+ private readonly waiters: Array<(result: IteratorResult<T>) => void> = []
437
+ private closed = false
438
+
439
+ push(value: T) {
440
+ if (this.closed) {
441
+ throw new Error("Cannot push to a closed queue")
442
+ }
443
+
444
+ const waiter = this.waiters.shift()
445
+ if (waiter) {
446
+ waiter({ done: false, value })
447
+ return
448
+ }
449
+
450
+ this.values.push(value)
451
+ }
452
+
453
+ close() {
454
+ if (this.closed) return
455
+ this.closed = true
456
+ while (this.waiters.length > 0) {
457
+ const waiter = this.waiters.shift()
458
+ waiter?.({ done: true, value: undefined as never })
459
+ }
460
+ }
461
+
462
+ [Symbol.asyncIterator](): AsyncIterator<T> {
463
+ return {
464
+ next: async () => {
465
+ if (this.values.length > 0) {
466
+ return { done: false, value: this.values.shift() as T }
467
+ }
468
+
469
+ if (this.closed) {
470
+ return { done: true, value: undefined as never }
471
+ }
472
+
473
+ return await new Promise<IteratorResult<T>>((resolve) => {
474
+ this.waiters.push(resolve)
475
+ })
476
+ },
477
+ }
478
+ }
479
+ }
480
+
481
+ async function startClaudeSession(args: {
274
482
  localPath: string
275
483
  model: string
276
484
  effort?: string
277
485
  planMode: boolean
278
486
  sessionToken: string | null
279
487
  onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
280
- }): Promise<HarnessTurn> {
488
+ }): Promise<ClaudeSessionHandle> {
281
489
  const canUseTool: CanUseTool = async (toolName, input, options) => {
282
490
  if (toolName !== "AskUserQuestion" && toolName !== "ExitPlanMode") {
283
491
  return {
@@ -333,8 +541,10 @@ async function startClaudeTurn(args: {
333
541
  } satisfies PermissionResult
334
542
  }
335
543
 
544
+ const promptQueue = new AsyncMessageQueue<SDKUserMessage>()
545
+
336
546
  const q = query({
337
- prompt: args.content,
547
+ prompt: promptQueue,
338
548
  options: {
339
549
  cwd: args.localPath,
340
550
  model: args.model,
@@ -361,7 +571,25 @@ async function startClaudeTurn(args: {
361
571
  interrupt: async () => {
362
572
  await q.interrupt()
363
573
  },
574
+ sendPrompt: async (content: string) => {
575
+ promptQueue.push({
576
+ type: "user",
577
+ message: {
578
+ role: "user",
579
+ content,
580
+ },
581
+ parent_tool_use_id: null,
582
+ session_id: args.sessionToken ?? "",
583
+ })
584
+ },
585
+ setModel: async (model: string) => {
586
+ await q.setModel(model)
587
+ },
588
+ setPermissionMode: async (planMode: boolean) => {
589
+ await q.setPermissionMode(planMode ? "plan" : "acceptEdits")
590
+ },
364
591
  close: () => {
592
+ promptQueue.close()
365
593
  q.close()
366
594
  },
367
595
  }
@@ -372,14 +600,18 @@ export class AgentCoordinator {
372
600
  private readonly onStateChange: () => void
373
601
  private readonly codexManager: CodexAppServerManager
374
602
  private readonly generateTitle: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
603
+ private readonly startClaudeSessionFn: NonNullable<AgentCoordinatorArgs["startClaudeSession"]>
375
604
  private reportBackgroundError: ((message: string) => void) | null = null
376
605
  readonly activeTurns = new Map<string, ActiveTurn>()
606
+ readonly drainingStreams = new Map<string, { turn: HarnessTurn }>()
607
+ readonly claudeSessions = new Map<string, ClaudeSessionState>()
377
608
 
378
609
  constructor(args: AgentCoordinatorArgs) {
379
610
  this.store = args.store
380
611
  this.onStateChange = args.onStateChange
381
612
  this.codexManager = args.codexManager ?? new CodexAppServerManager()
382
613
  this.generateTitle = args.generateTitle ?? generateTitleForChatDetailed
614
+ this.startClaudeSessionFn = args.startClaudeSession ?? startClaudeSession
383
615
  }
384
616
 
385
617
  setBackgroundErrorReporter(report: ((message: string) => void) | null) {
@@ -400,6 +632,28 @@ export class AgentCoordinator {
400
632
  return { toolUseId: pending.toolUseId, toolKind: pending.tool.toolKind }
401
633
  }
402
634
 
635
+ getDrainingChatIds(): Set<string> {
636
+ return new Set(this.drainingStreams.keys())
637
+ }
638
+
639
+ async stopDraining(chatId: string) {
640
+ const draining = this.drainingStreams.get(chatId)
641
+ if (!draining) return
642
+ draining.turn.close()
643
+ this.drainingStreams.delete(chatId)
644
+ this.onStateChange()
645
+ }
646
+
647
+ async closeChat(chatId: string) {
648
+ await this.stopDraining(chatId)
649
+ const claudeSession = this.claudeSessions.get(chatId)
650
+ if (claudeSession) {
651
+ claudeSession.session.close()
652
+ this.claudeSessions.delete(chatId)
653
+ }
654
+ this.onStateChange()
655
+ }
656
+
403
657
  private resolveProvider(command: Extract<ClientCommand, { type: "chat.send" }>, currentProvider: AgentProvider | null) {
404
658
  if (currentProvider) return currentProvider
405
659
  return command.provider ?? "claude"
@@ -438,6 +692,13 @@ export class AgentCoordinator {
438
692
  planMode: boolean
439
693
  appendUserPrompt: boolean
440
694
  }) {
695
+ // Close any lingering draining stream before starting a new turn.
696
+ const draining = this.drainingStreams.get(args.chatId)
697
+ if (draining) {
698
+ draining.turn.close()
699
+ this.drainingStreams.delete(args.chatId)
700
+ }
701
+
441
702
  const chat = this.store.requireChat(args.chatId)
442
703
  if (this.activeTurns.has(args.chatId)) {
443
704
  throw new Error("Chat is already running")
@@ -456,19 +717,20 @@ export class AgentCoordinator {
456
717
  await this.store.renameChat(args.chatId, optimisticTitle)
457
718
  }
458
719
 
459
- if (args.appendUserPrompt) {
460
- await this.store.appendMessage(
461
- args.chatId,
462
- timestamped({ kind: "user_prompt", content: args.content, attachments: args.attachments }, Date.now())
463
- )
464
- }
465
- await this.store.recordTurnStarted(args.chatId)
466
-
467
720
  const project = this.store.getProject(chat.projectId)
468
721
  if (!project) {
469
722
  throw new Error("Project not found")
470
723
  }
471
724
 
725
+ if (args.appendUserPrompt) {
726
+ const userPromptEntry = timestamped(
727
+ { kind: "user_prompt", content: args.content, attachments: args.attachments },
728
+ Date.now()
729
+ )
730
+ await this.store.appendMessage(args.chatId, userPromptEntry)
731
+ }
732
+ await this.store.recordTurnStarted(args.chatId)
733
+
472
734
  if (shouldGenerateTitle) {
473
735
  void this.generateTitleInBackground(args.chatId, args.content, project.localPath, optimisticTitle ?? "New Chat")
474
736
  }
@@ -493,8 +755,8 @@ export class AgentCoordinator {
493
755
 
494
756
  let turn: HarnessTurn
495
757
  if (args.provider === "claude") {
496
- turn = await startClaudeTurn({
497
- content: buildPromptText(args.content, args.attachments),
758
+ turn = await this.startClaudeTurn({
759
+ chatId: args.chatId,
498
760
  localPath: project.localPath,
499
761
  model: args.model,
500
762
  effort: args.effort,
@@ -529,7 +791,7 @@ export class AgentCoordinator {
529
791
  effort: args.effort,
530
792
  serviceTier: args.serviceTier,
531
793
  planMode: args.planMode,
532
- status: "starting",
794
+ status: args.provider === "claude" ? "running" : "starting",
533
795
  pendingTool: null,
534
796
  postToolFollowUp: null,
535
797
  hasFinalResult: false,
@@ -543,15 +805,93 @@ export class AgentCoordinator {
543
805
  void turn.getAccountInfo()
544
806
  .then(async (accountInfo) => {
545
807
  if (!accountInfo) return
808
+ if (args.provider === "claude") {
809
+ const session = this.claudeSessions.get(args.chatId)
810
+ if (session) {
811
+ if (session.accountInfoLoaded) return
812
+ session.accountInfoLoaded = true
813
+ } else {
814
+ return
815
+ }
816
+ }
546
817
  await this.store.appendMessage(args.chatId, timestamped({ kind: "account_info", accountInfo }))
547
818
  this.onStateChange()
548
819
  })
549
820
  .catch(() => undefined)
550
821
  }
551
822
 
823
+ if (args.provider === "claude") {
824
+ const session = this.claudeSessions.get(args.chatId)
825
+ if (!session) {
826
+ throw new Error("Claude session was not initialized")
827
+ }
828
+ await session.session.sendPrompt(buildPromptText(args.content, args.attachments))
829
+ return
830
+ }
831
+
552
832
  void this.runTurn(active)
553
833
  }
554
834
 
835
+ private async startClaudeTurn(args: {
836
+ chatId: string
837
+ localPath: string
838
+ model: string
839
+ effort?: string
840
+ planMode: boolean
841
+ sessionToken: string | null
842
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
843
+ }): Promise<HarnessTurn> {
844
+ let session = this.claudeSessions.get(args.chatId)
845
+
846
+ if (!session || session.localPath !== args.localPath || session.effort !== args.effort) {
847
+ if (session) {
848
+ session.session.close()
849
+ this.claudeSessions.delete(args.chatId)
850
+ }
851
+
852
+ const started = await this.startClaudeSessionFn({
853
+ localPath: args.localPath,
854
+ model: args.model,
855
+ effort: args.effort,
856
+ planMode: args.planMode,
857
+ sessionToken: args.sessionToken,
858
+ onToolRequest: args.onToolRequest,
859
+ })
860
+
861
+ session = {
862
+ chatId: args.chatId,
863
+ session: started,
864
+ localPath: args.localPath,
865
+ model: args.model,
866
+ effort: args.effort,
867
+ planMode: args.planMode,
868
+ sessionToken: args.sessionToken,
869
+ accountInfoLoaded: false,
870
+ }
871
+ this.claudeSessions.set(args.chatId, session)
872
+ void this.runClaudeSession(session)
873
+ } else {
874
+ if (session.model !== args.model) {
875
+ await session.session.setModel(args.model)
876
+ session.model = args.model
877
+ }
878
+ if (session.planMode !== args.planMode) {
879
+ await session.session.setPermissionMode(args.planMode)
880
+ session.planMode = args.planMode
881
+ }
882
+ }
883
+
884
+ return {
885
+ provider: "claude",
886
+ stream: {
887
+ async *[Symbol.asyncIterator]() {},
888
+ },
889
+ getAccountInfo: session.session.getAccountInfo,
890
+ interrupt: session.session.interrupt,
891
+ close: () => {},
892
+ }
893
+ }
894
+
555
895
  async send(command: Extract<ClientCommand, { type: "chat.send" }>) {
556
896
  let chatId = command.chatId
557
897
 
@@ -581,6 +921,66 @@ export class AgentCoordinator {
581
921
  return { chatId }
582
922
  }
583
923
 
924
+ private async runClaudeSession(session: ClaudeSessionState) {
925
+ try {
926
+ for await (const event of session.session.stream) {
927
+ if (event.type === "session_token" && event.sessionToken) {
928
+ session.sessionToken = event.sessionToken
929
+ await this.store.setSessionToken(session.chatId, event.sessionToken)
930
+ this.onStateChange()
931
+ continue
932
+ }
933
+
934
+ if (!event.entry) continue
935
+ await this.store.appendMessage(session.chatId, event.entry)
936
+
937
+ const active = this.activeTurns.get(session.chatId)
938
+ if (event.entry.kind === "system_init" && active) {
939
+ active.status = "running"
940
+ }
941
+
942
+ if (event.entry.kind === "result" && active) {
943
+ active.hasFinalResult = true
944
+ if (event.entry.isError) {
945
+ await this.store.recordTurnFailed(session.chatId, event.entry.result || "Turn failed")
946
+ } else if (!active.cancelRequested) {
947
+ await this.store.recordTurnFinished(session.chatId)
948
+ }
949
+ this.activeTurns.delete(session.chatId)
950
+ }
951
+
952
+ this.onStateChange()
953
+ }
954
+ } catch (error) {
955
+ const active = this.activeTurns.get(session.chatId)
956
+ if (active && !active.cancelRequested) {
957
+ const message = error instanceof Error ? error.message : String(error)
958
+ await this.store.appendMessage(
959
+ session.chatId,
960
+ timestamped({
961
+ kind: "result",
962
+ subtype: "error",
963
+ isError: true,
964
+ durationMs: 0,
965
+ result: message,
966
+ })
967
+ )
968
+ await this.store.recordTurnFailed(session.chatId, message)
969
+ }
970
+ } finally {
971
+ this.claudeSessions.delete(session.chatId)
972
+ const active = this.activeTurns.get(session.chatId)
973
+ if (active?.provider === "claude") {
974
+ if (active.cancelRequested && !active.cancelRecorded) {
975
+ await this.store.recordTurnCancelled(session.chatId)
976
+ }
977
+ this.activeTurns.delete(session.chatId)
978
+ }
979
+ session.session.close()
980
+ this.onStateChange()
981
+ }
982
+ }
983
+
584
984
  private async generateTitleInBackground(chatId: string, messageContent: string, cwd: string, expectedCurrentTitle: string) {
585
985
  try {
586
986
  const result = await this.generateTitle(messageContent, cwd)
@@ -607,6 +1007,10 @@ export class AgentCoordinator {
607
1007
  private async runTurn(active: ActiveTurn) {
608
1008
  try {
609
1009
  for await (const event of active.turn.stream) {
1010
+ // Once cancelled, stop processing further stream events.
1011
+ // cancel() already removed us from activeTurns and notified the UI.
1012
+ if (active.cancelRequested) break
1013
+
610
1014
  if (event.type === "session_token" && event.sessionToken) {
611
1015
  await this.store.setSessionToken(active.chatId, event.sessionToken)
612
1016
  this.onStateChange()
@@ -627,6 +1031,14 @@ export class AgentCoordinator {
627
1031
  } else if (!active.cancelRequested) {
628
1032
  await this.store.recordTurnFinished(active.chatId)
629
1033
  }
1034
+ // Remove from activeTurns as soon as the result arrives so the UI
1035
+ // transitions to idle immediately. The stream may still be open
1036
+ // (e.g. background tasks), but the user should be able to send
1037
+ // new messages without having to hit stop first.
1038
+ this.activeTurns.delete(active.chatId)
1039
+ // Track the still-open stream so the UI can show a draining
1040
+ // indicator and the user can stop background tasks.
1041
+ this.drainingStreams.set(active.chatId, { turn: active.turn })
630
1042
  }
631
1043
 
632
1044
  this.onStateChange()
@@ -651,7 +1063,14 @@ export class AgentCoordinator {
651
1063
  await this.store.recordTurnCancelled(active.chatId)
652
1064
  }
653
1065
  active.turn.close()
654
- this.activeTurns.delete(active.chatId)
1066
+ // Only remove if we're still the active turn for this chat.
1067
+ // We may have already been removed by result handling or cancel(),
1068
+ // and a new turn may have started for the same chatId.
1069
+ if (this.activeTurns.get(active.chatId) === active) {
1070
+ this.activeTurns.delete(active.chatId)
1071
+ }
1072
+ // Stream has fully ended — no longer draining.
1073
+ this.drainingStreams.delete(active.chatId)
655
1074
  this.onStateChange()
656
1075
 
657
1076
  if (active.postToolFollowUp && !active.cancelRequested) {
@@ -687,9 +1106,18 @@ export class AgentCoordinator {
687
1106
  }
688
1107
 
689
1108
  async cancel(chatId: string) {
1109
+ // Also clean up any draining stream for this chat.
1110
+ const draining = this.drainingStreams.get(chatId)
1111
+ if (draining) {
1112
+ draining.turn.close()
1113
+ this.drainingStreams.delete(chatId)
1114
+ }
1115
+
690
1116
  const active = this.activeTurns.get(chatId)
691
1117
  if (!active) return
692
1118
 
1119
+ // Guard against concurrent cancel() calls — only the first one does work.
1120
+ if (active.cancelRequested) return
693
1121
  active.cancelRequested = true
694
1122
 
695
1123
  const pendingTool = active.pendingTool
@@ -715,14 +1143,23 @@ export class AgentCoordinator {
715
1143
  active.cancelRecorded = true
716
1144
  active.hasFinalResult = true
717
1145
 
1146
+ // Remove from activeTurns immediately so the UI reflects the cancellation
1147
+ // right away, rather than waiting for interrupt() which may hang.
1148
+ this.activeTurns.delete(chatId)
1149
+ this.onStateChange()
1150
+
1151
+ // Now attempt to interrupt/close the underlying stream in the background.
1152
+ // This is best-effort — the turn is already removed from active state above,
1153
+ // and runTurn()'s finally block will also call close().
718
1154
  try {
719
- await active.turn.interrupt()
1155
+ await Promise.race([
1156
+ active.turn.interrupt(),
1157
+ new Promise((resolve) => setTimeout(resolve, 5_000)),
1158
+ ])
720
1159
  } catch {
721
- active.turn.close()
1160
+ // interrupt() failed — force close
722
1161
  }
723
-
724
- this.activeTurns.delete(chatId)
725
- this.onStateChange()
1162
+ active.turn.close()
726
1163
  }
727
1164
 
728
1165
  async respondTool(command: Extract<ClientCommand, { type: "chat.respondTool" }>) {