@cuongtran001/kanna 0.39.2

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 (473) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +355 -0
  3. package/bin/kanna +9 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  6. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  7. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  8. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  9. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  10. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  11. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  12. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  13. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  14. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  15. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  16. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  17. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  18. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  22. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  23. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  24. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  25. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  26. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  27. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  28. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  29. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  30. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  31. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  32. package/dist/client/assets/bricolage-grotesque-latin-ext-wght-normal-CcLUaPy7.woff2 +0 -0
  33. package/dist/client/assets/bricolage-grotesque-latin-wght-normal-DLoelf7F.woff2 +0 -0
  34. package/dist/client/assets/bricolage-grotesque-vietnamese-wght-normal-BUzh504Q.woff2 +0 -0
  35. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  36. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  37. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  38. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  39. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  40. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  41. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  42. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  43. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  44. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  45. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  46. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  47. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  48. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  49. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  50. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  51. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  52. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  53. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  54. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  55. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  56. package/dist/client/assets/css-DPfMkruS.js +1 -0
  57. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  58. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  59. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  60. package/dist/client/assets/d-85-TOEBH.js +1 -0
  61. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  62. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  63. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  64. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  65. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  66. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  67. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  68. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  69. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  70. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  71. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  72. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  73. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  74. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  75. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  76. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  77. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  78. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  79. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  80. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  81. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  82. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  83. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  84. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  85. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  86. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  87. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  88. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  89. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  90. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  91. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  92. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  93. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  94. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  95. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  96. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  97. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  98. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  99. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  100. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  101. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  102. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  103. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  104. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  105. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  106. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  107. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  108. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  109. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  110. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  111. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  112. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  113. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  114. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  115. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  116. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  117. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  118. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  119. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  120. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  121. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  122. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  123. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  124. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  125. package/dist/client/assets/html-GMplVEZG.js +1 -0
  126. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  127. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  128. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  129. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  130. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  131. package/dist/client/assets/imba-DGztddWO.js +1 -0
  132. package/dist/client/assets/index-Do7324M0.css +32 -0
  133. package/dist/client/assets/index-ktE9DLCD.js +2620 -0
  134. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  135. package/dist/client/assets/java-CylS5w8V.js +1 -0
  136. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  137. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  138. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  139. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  140. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  141. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  142. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  143. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  144. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  145. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  146. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  147. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  148. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  149. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  150. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  151. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  152. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  153. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  154. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  155. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  156. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  157. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  158. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  159. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  160. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  161. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  162. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  163. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  164. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  165. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  166. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  167. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  168. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  169. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  170. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  171. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  172. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  173. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  174. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  175. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  176. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  177. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  178. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  179. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  180. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  181. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  182. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  183. package/dist/client/assets/move-IF9eRakj.js +1 -0
  184. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  185. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  186. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  187. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  188. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  189. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  190. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  191. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  192. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  193. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  194. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  195. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  196. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  197. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  198. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  199. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  200. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  201. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  202. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  203. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  204. package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
  205. package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
  206. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  207. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  208. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  209. package/dist/client/assets/po-BTJTHyun.js +1 -0
  210. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  211. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  212. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  213. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  214. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  215. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  216. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  217. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  218. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  219. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  220. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  221. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  222. package/dist/client/assets/qml-3beO22l8.js +1 -0
  223. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  224. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  225. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  226. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  227. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  228. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  229. package/dist/client/assets/red-bN70gL4F.js +1 -0
  230. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  231. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  232. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  233. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  234. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  235. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  236. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  237. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  238. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  239. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  240. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  241. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  242. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  243. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  244. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  245. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  246. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  247. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  248. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  249. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  250. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  251. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  252. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  253. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  254. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  255. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  256. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  257. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  258. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  259. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  260. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  261. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  262. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  263. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  264. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  265. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  266. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  267. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  268. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  269. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  270. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  271. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  272. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  273. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  274. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  275. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  276. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  277. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  278. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  279. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  280. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  281. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  282. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  283. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  284. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  285. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  286. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  287. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  288. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  289. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  290. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  291. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  292. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  293. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  294. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  295. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  296. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  297. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  298. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  299. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  300. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  301. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  302. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  303. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  304. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  305. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  306. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  307. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  308. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  309. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  310. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  311. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  312. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  313. package/dist/client/chat-sounds/Blow.mp3 +0 -0
  314. package/dist/client/chat-sounds/Bottle.mp3 +0 -0
  315. package/dist/client/chat-sounds/Frog.mp3 +0 -0
  316. package/dist/client/chat-sounds/Funk.mp3 +0 -0
  317. package/dist/client/chat-sounds/Glass.mp3 +0 -0
  318. package/dist/client/chat-sounds/Ping.mp3 +0 -0
  319. package/dist/client/chat-sounds/Pop.mp3 +0 -0
  320. package/dist/client/chat-sounds/Purr.mp3 +0 -0
  321. package/dist/client/chat-sounds/Tink.mp3 +0 -0
  322. package/dist/client/editor-icons/cursor.png +0 -0
  323. package/dist/client/editor-icons/custom.png +0 -0
  324. package/dist/client/editor-icons/default-app.png +0 -0
  325. package/dist/client/editor-icons/finder.png +0 -0
  326. package/dist/client/editor-icons/preview.png +0 -0
  327. package/dist/client/editor-icons/terminal.png +0 -0
  328. package/dist/client/editor-icons/windsurf.png +0 -0
  329. package/dist/client/editor-icons/xcode.png +0 -0
  330. package/dist/client/favicon.png +0 -0
  331. package/dist/client/fonts/body-medium.woff2 +0 -0
  332. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  333. package/dist/client/fonts/body-regular.woff2 +0 -0
  334. package/dist/client/fonts/body-semibold.woff2 +0 -0
  335. package/dist/client/icon-192.png +0 -0
  336. package/dist/client/icon-512.png +0 -0
  337. package/dist/client/icon-maskable-512.png +0 -0
  338. package/dist/client/icon.svg +4 -0
  339. package/dist/client/index.html +34 -0
  340. package/dist/client/manifest.webmanifest +46 -0
  341. package/dist/client/screenshot-light.png +0 -0
  342. package/dist/client/screenshot.png +0 -0
  343. package/dist/export-viewer/assets/bricolage-grotesque-latin-ext-wght-normal-CcLUaPy7.woff2 +0 -0
  344. package/dist/export-viewer/assets/bricolage-grotesque-latin-wght-normal-DLoelf7F.woff2 +0 -0
  345. package/dist/export-viewer/assets/bricolage-grotesque-vietnamese-wght-normal-BUzh504Q.woff2 +0 -0
  346. package/dist/export-viewer/assets/index-D1qUumZR.js +410 -0
  347. package/dist/export-viewer/assets/index-gG2nMW51.css +1 -0
  348. package/dist/export-viewer/editor-icons/cursor.png +0 -0
  349. package/dist/export-viewer/editor-icons/custom.png +0 -0
  350. package/dist/export-viewer/editor-icons/default-app.png +0 -0
  351. package/dist/export-viewer/editor-icons/finder.png +0 -0
  352. package/dist/export-viewer/editor-icons/preview.png +0 -0
  353. package/dist/export-viewer/editor-icons/terminal.png +0 -0
  354. package/dist/export-viewer/editor-icons/windsurf.png +0 -0
  355. package/dist/export-viewer/editor-icons/xcode.png +0 -0
  356. package/dist/export-viewer/fonts/body-medium.woff2 +0 -0
  357. package/dist/export-viewer/fonts/body-regular-italic.woff2 +0 -0
  358. package/dist/export-viewer/fonts/body-regular.woff2 +0 -0
  359. package/dist/export-viewer/fonts/body-semibold.woff2 +0 -0
  360. package/dist/export-viewer/index.html +14 -0
  361. package/package.json +99 -0
  362. package/src/server/__fixtures__/claude-session-empty.jsonl +0 -0
  363. package/src/server/__fixtures__/claude-session-malformed.jsonl +3 -0
  364. package/src/server/__fixtures__/claude-session-valid.jsonl +6 -0
  365. package/src/server/agent.test.ts +2369 -0
  366. package/src/server/agent.ts +1927 -0
  367. package/src/server/analytics.test.ts +313 -0
  368. package/src/server/analytics.ts +131 -0
  369. package/src/server/app-settings.test.ts +233 -0
  370. package/src/server/app-settings.ts +548 -0
  371. package/src/server/auth.test.ts +329 -0
  372. package/src/server/auth.ts +204 -0
  373. package/src/server/auto-continue/e2e.test.ts +215 -0
  374. package/src/server/auto-continue/events.test.ts +30 -0
  375. package/src/server/auto-continue/events.ts +35 -0
  376. package/src/server/auto-continue/limit-detector.test.ts +153 -0
  377. package/src/server/auto-continue/limit-detector.ts +159 -0
  378. package/src/server/auto-continue/read-model.test.ts +109 -0
  379. package/src/server/auto-continue/read-model.ts +83 -0
  380. package/src/server/auto-continue/schedule-manager.test.ts +155 -0
  381. package/src/server/auto-continue/schedule-manager.ts +116 -0
  382. package/src/server/claude-session-importer.test.ts +214 -0
  383. package/src/server/claude-session-importer.ts +187 -0
  384. package/src/server/claude-session-mapper.test.ts +88 -0
  385. package/src/server/claude-session-mapper.ts +106 -0
  386. package/src/server/claude-session-parser.test.ts +38 -0
  387. package/src/server/claude-session-parser.ts +67 -0
  388. package/src/server/claude-session-scanner.test.ts +49 -0
  389. package/src/server/claude-session-scanner.ts +24 -0
  390. package/src/server/claude-session-types.ts +61 -0
  391. package/src/server/cli-runtime.test.ts +523 -0
  392. package/src/server/cli-runtime.ts +405 -0
  393. package/src/server/cli-supervisor.ts +102 -0
  394. package/src/server/cli.ts +64 -0
  395. package/src/server/cloudflare-tunnel/agent-integration.test.ts +76 -0
  396. package/src/server/cloudflare-tunnel/agent-integration.ts +55 -0
  397. package/src/server/cloudflare-tunnel/detector.test.ts +72 -0
  398. package/src/server/cloudflare-tunnel/detector.ts +44 -0
  399. package/src/server/cloudflare-tunnel/e2e.test.ts +194 -0
  400. package/src/server/cloudflare-tunnel/events.test.ts +43 -0
  401. package/src/server/cloudflare-tunnel/events.ts +31 -0
  402. package/src/server/cloudflare-tunnel/gateway.ts +143 -0
  403. package/src/server/cloudflare-tunnel/lifecycle.test.ts +48 -0
  404. package/src/server/cloudflare-tunnel/lifecycle.ts +62 -0
  405. package/src/server/cloudflare-tunnel/read-model.test.ts +69 -0
  406. package/src/server/cloudflare-tunnel/read-model.ts +80 -0
  407. package/src/server/cloudflare-tunnel/tunnel-manager.test.ts +116 -0
  408. package/src/server/cloudflare-tunnel/tunnel-manager.ts +165 -0
  409. package/src/server/codex-app-server-protocol.ts +487 -0
  410. package/src/server/codex-app-server.test.ts +1816 -0
  411. package/src/server/codex-app-server.ts +1475 -0
  412. package/src/server/diff-store.test.ts +737 -0
  413. package/src/server/diff-store.ts +2199 -0
  414. package/src/server/discovery.test.ts +211 -0
  415. package/src/server/discovery.ts +301 -0
  416. package/src/server/event-store.test.ts +797 -0
  417. package/src/server/event-store.ts +1421 -0
  418. package/src/server/events.ts +217 -0
  419. package/src/server/external-open.test.ts +112 -0
  420. package/src/server/external-open.ts +345 -0
  421. package/src/server/generate-commit-message.test.ts +79 -0
  422. package/src/server/generate-commit-message.ts +126 -0
  423. package/src/server/generate-title.ts +76 -0
  424. package/src/server/harness-types.ts +19 -0
  425. package/src/server/keybindings.test.ts +144 -0
  426. package/src/server/keybindings.ts +178 -0
  427. package/src/server/llm-provider.test.ts +134 -0
  428. package/src/server/llm-provider.ts +207 -0
  429. package/src/server/machine-name.ts +22 -0
  430. package/src/server/paths-route.test.ts +64 -0
  431. package/src/server/paths.ts +35 -0
  432. package/src/server/process-utils.test.ts +12 -0
  433. package/src/server/process-utils.ts +47 -0
  434. package/src/server/project-paths.test.ts +95 -0
  435. package/src/server/project-paths.ts +191 -0
  436. package/src/server/provider-catalog.test.ts +69 -0
  437. package/src/server/provider-catalog.ts +87 -0
  438. package/src/server/quick-response.test.ts +440 -0
  439. package/src/server/quick-response.ts +300 -0
  440. package/src/server/read-models.test.ts +509 -0
  441. package/src/server/read-models.ts +230 -0
  442. package/src/server/restart.test.ts +27 -0
  443. package/src/server/restart.ts +30 -0
  444. package/src/server/server.ts +616 -0
  445. package/src/server/share.test.ts +180 -0
  446. package/src/server/share.ts +150 -0
  447. package/src/server/standalone-export.test.ts +224 -0
  448. package/src/server/standalone-export.ts +419 -0
  449. package/src/server/terminal-manager.test.ts +315 -0
  450. package/src/server/terminal-manager.ts +350 -0
  451. package/src/server/test-helpers/async-event-queue.ts +52 -0
  452. package/src/server/test-helpers/wait-for.ts +14 -0
  453. package/src/server/title-generation.live.test.ts +44 -0
  454. package/src/server/update-manager.test.ts +158 -0
  455. package/src/server/update-manager.ts +222 -0
  456. package/src/server/update-strategy.test.ts +237 -0
  457. package/src/server/update-strategy.ts +241 -0
  458. package/src/server/uploads.test.ts +292 -0
  459. package/src/server/uploads.ts +131 -0
  460. package/src/server/ws-router.test.ts +2292 -0
  461. package/src/server/ws-router.ts +1465 -0
  462. package/src/shared/analytics.ts +30 -0
  463. package/src/shared/branding.test.ts +31 -0
  464. package/src/shared/branding.ts +77 -0
  465. package/src/shared/dev-ports.test.ts +113 -0
  466. package/src/shared/dev-ports.ts +134 -0
  467. package/src/shared/ports.ts +2 -0
  468. package/src/shared/protocol.ts +257 -0
  469. package/src/shared/share.ts +27 -0
  470. package/src/shared/tools.test.ts +164 -0
  471. package/src/shared/tools.ts +327 -0
  472. package/src/shared/types.test.ts +25 -0
  473. package/src/shared/types.ts +1088 -0
@@ -0,0 +1,1475 @@
1
+ import { spawn } from "node:child_process"
2
+ import { randomUUID } from "node:crypto"
3
+ import { createInterface } from "node:readline"
4
+ import type { Readable, Writable } from "node:stream"
5
+ import type {
6
+ AskUserQuestionItem,
7
+ CodexReasoningEffort,
8
+ ContextWindowUsageSnapshot,
9
+ ServiceTier,
10
+ TodoItem,
11
+ TranscriptEntry,
12
+ } from "../shared/types"
13
+ import type { HarnessEvent, HarnessToolRequest, HarnessTurn } from "./harness-types"
14
+ import {
15
+ type CollabAgentToolCallItem,
16
+ type ContextCompactedNotification,
17
+ type CodexRequestId,
18
+ type CommandExecutionApprovalDecision,
19
+ type CommandExecutionRequestApprovalParams,
20
+ type CommandExecutionRequestApprovalResponse,
21
+ type DynamicToolCallOutputContentItem,
22
+ type DynamicToolCallResponse,
23
+ type FileChangeApprovalDecision,
24
+ type FileChangeRequestApprovalParams,
25
+ type FileChangeRequestApprovalResponse,
26
+ type InitializeParams,
27
+ type ItemCompletedNotification,
28
+ type ItemStartedNotification,
29
+ type JsonRpcResponse,
30
+ type McpToolCallItem,
31
+ type PlanDeltaNotification,
32
+ type ServerNotification,
33
+ type ServerRequest,
34
+ type ThreadItem,
35
+ type ThreadResumeParams,
36
+ type ThreadResumeResponse,
37
+ type ThreadForkParams,
38
+ type ThreadForkResponse,
39
+ type ThreadStartParams,
40
+ type ThreadStartResponse,
41
+ type ThreadTokenUsageUpdatedNotification,
42
+ type ToolRequestUserInputParams,
43
+ type ToolRequestUserInputQuestion,
44
+ type ToolRequestUserInputResponse,
45
+ type TurnPlanStep,
46
+ type TurnPlanUpdatedNotification,
47
+ type TurnCompletedNotification,
48
+ type TurnInterruptParams,
49
+ type TurnStartParams,
50
+ type TurnStartResponse,
51
+ isJsonRpcResponse,
52
+ isServerNotification,
53
+ isServerRequest,
54
+ } from "./codex-app-server-protocol"
55
+
56
+ interface CodexAppServerProcess {
57
+ stdin: Writable
58
+ stdout: Readable
59
+ stderr: Readable
60
+ killed?: boolean
61
+ kill(signal?: NodeJS.Signals | number): void
62
+ on(event: "close", listener: (code: number | null) => void): this
63
+ on(event: "error", listener: (error: Error) => void): this
64
+ once(event: "close", listener: (code: number | null) => void): this
65
+ once(event: "error", listener: (error: Error) => void): this
66
+ }
67
+
68
+ type SpawnCodexAppServer = (cwd: string) => CodexAppServerProcess
69
+
70
+ interface PendingRequest<TResult> {
71
+ method: string
72
+ resolve: (value: TResult) => void
73
+ reject: (error: Error) => void
74
+ }
75
+
76
+ interface PendingTurn {
77
+ turnId: string | null
78
+ model: string
79
+ planMode: boolean
80
+ queue: AsyncQueue<HarnessEvent>
81
+ startedToolIds: Set<string>
82
+ handledDynamicToolIds: Set<string>
83
+ latestPlanExplanation: string | null
84
+ latestPlanSteps: TurnPlanStep[]
85
+ latestPlanText: string | null
86
+ planTextByItemId: Map<string, string>
87
+ todoSequence: number
88
+ pendingWebSearchResultToolId: string | null
89
+ resolved: boolean
90
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
91
+ onApprovalRequest?: (
92
+ request:
93
+ | {
94
+ requestId: CodexRequestId
95
+ kind: "command_execution"
96
+ params: CommandExecutionRequestApprovalParams
97
+ }
98
+ | {
99
+ requestId: CodexRequestId
100
+ kind: "file_change"
101
+ params: FileChangeRequestApprovalParams
102
+ }
103
+ ) => Promise<CommandExecutionApprovalDecision | FileChangeApprovalDecision>
104
+ }
105
+
106
+ interface SessionContext {
107
+ chatId: string
108
+ cwd: string
109
+ child: CodexAppServerProcess
110
+ pendingRequests: Map<CodexRequestId, PendingRequest<unknown>>
111
+ pendingTurn: PendingTurn | null
112
+ sessionToken: string | null
113
+ stderrLines: string[]
114
+ closed: boolean
115
+ }
116
+
117
+ export interface StartCodexSessionArgs {
118
+ chatId: string
119
+ cwd: string
120
+ model: string
121
+ serviceTier?: ServiceTier
122
+ sessionToken: string | null
123
+ pendingForkSessionToken?: string | null
124
+ }
125
+
126
+ export interface StartCodexTurnArgs {
127
+ chatId: string
128
+ model: string
129
+ effort?: CodexReasoningEffort
130
+ serviceTier?: ServiceTier
131
+ content: string
132
+ planMode: boolean
133
+ onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
134
+ onApprovalRequest?: PendingTurn["onApprovalRequest"]
135
+ }
136
+
137
+ export interface GenerateStructuredArgs {
138
+ cwd: string
139
+ prompt: string
140
+ model?: string
141
+ effort?: CodexReasoningEffort
142
+ serviceTier?: ServiceTier
143
+ }
144
+
145
+ function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
146
+ entry: T,
147
+ createdAt = Date.now()
148
+ ): TranscriptEntry {
149
+ return {
150
+ _id: randomUUID(),
151
+ createdAt,
152
+ ...entry,
153
+ } as TranscriptEntry
154
+ }
155
+
156
+ function codexSystemInitEntry(model: string): TranscriptEntry {
157
+ return timestamped({
158
+ kind: "system_init",
159
+ provider: "codex",
160
+ model,
161
+ tools: ["Bash", "Write", "Edit", "WebSearch", "TodoWrite", "AskUserQuestion", "ExitPlanMode"],
162
+ agents: ["spawnAgent", "sendInput", "resumeAgent", "wait", "closeAgent"],
163
+ slashCommands: [],
164
+ mcpServers: [],
165
+ })
166
+ }
167
+
168
+ function errorMessage(value: unknown): string {
169
+ if (value instanceof Error) return value.message
170
+ return String(value)
171
+ }
172
+
173
+ function parseJsonLine(line: string): unknown | null {
174
+ try {
175
+ return JSON.parse(line)
176
+ } catch {
177
+ return null
178
+ }
179
+ }
180
+
181
+ function isRecoverableResumeError(error: unknown): boolean {
182
+ const message = errorMessage(error).toLowerCase()
183
+ if (!message.includes("thread/resume")) return false
184
+ return ["not found", "missing thread", "no such thread", "unknown thread", "does not exist"].some((snippet) =>
185
+ message.includes(snippet)
186
+ )
187
+ }
188
+
189
+ const MULTI_SELECT_HINT_PATTERN = /\b(all that apply|select all|choose all|pick all|select multiple|choose multiple|pick multiple|multiple selections?|multiple choice|more than one|one or more)\b/i
190
+
191
+ function inferQuestionAllowsMultiple(question: ToolRequestUserInputQuestion): boolean {
192
+ const combinedText = [question.header, question.question].filter(Boolean).join(" ")
193
+ return MULTI_SELECT_HINT_PATTERN.test(combinedText)
194
+ }
195
+
196
+ function toAskUserQuestionItems(params: ToolRequestUserInputParams): AskUserQuestionItem[] {
197
+ return params.questions.map((question) => ({
198
+ id: question.id,
199
+ question: question.question,
200
+ header: question.header || undefined,
201
+ options: question.options?.map((option) => ({
202
+ label: option.label,
203
+ description: option.description ?? undefined,
204
+ })),
205
+ multiSelect: inferQuestionAllowsMultiple(question),
206
+ }))
207
+ }
208
+
209
+ function toToolRequestUserInputResponse(raw: unknown, questions: ToolRequestUserInputParams["questions"]): ToolRequestUserInputResponse {
210
+ const record = raw && typeof raw === "object" ? raw as Record<string, unknown> : {}
211
+ const answersValue = record.answers
212
+ const value = answersValue && typeof answersValue === "object" && !Array.isArray(answersValue)
213
+ ? answersValue as Record<string, unknown>
214
+ : record
215
+ const answers = Object.fromEntries(
216
+ questions.map((question) => {
217
+ const rawAnswer = value[question.id] ?? value[question.question]
218
+ if (Array.isArray(rawAnswer)) {
219
+ return [question.id, { answers: rawAnswer.map((entry) => String(entry)) }]
220
+ }
221
+ if (typeof rawAnswer === "string") {
222
+ return [question.id, { answers: [rawAnswer] }]
223
+ }
224
+ if (rawAnswer && typeof rawAnswer === "object" && Array.isArray((rawAnswer as { answers?: unknown }).answers)) {
225
+ return [question.id, { answers: ((rawAnswer as { answers: unknown[] }).answers).map((entry) => String(entry)) }]
226
+ }
227
+ return [question.id, { answers: [] }]
228
+ })
229
+ )
230
+ return { answers }
231
+ }
232
+
233
+ function contentFromMcpResult(item: McpToolCallItem): unknown {
234
+ if (item.error?.message) {
235
+ return { error: item.error.message }
236
+ }
237
+ return item.result?.structuredContent ?? item.result?.content ?? null
238
+ }
239
+
240
+ function asRecord(value: unknown): Record<string, unknown> | null {
241
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null
242
+ return value as Record<string, unknown>
243
+ }
244
+
245
+ function asNumber(value: unknown): number | undefined {
246
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined
247
+ }
248
+
249
+ function normalizeCodexTokenUsage(
250
+ notification: ThreadTokenUsageUpdatedNotification,
251
+ ): ContextWindowUsageSnapshot | null {
252
+ const usage = notification.tokenUsage
253
+ const totalUsage = usage.total_token_usage ?? usage.total
254
+ const lastUsage = usage.last_token_usage ?? usage.last
255
+
256
+ const totalProcessedTokens = asNumber(totalUsage?.total_tokens) ?? asNumber(totalUsage?.totalTokens)
257
+ const usedTokens = asNumber(lastUsage?.total_tokens) ?? asNumber(lastUsage?.totalTokens) ?? totalProcessedTokens
258
+ if (usedTokens === undefined || usedTokens <= 0) {
259
+ return null
260
+ }
261
+
262
+ const inputTokens = asNumber(lastUsage?.input_tokens) ?? asNumber(lastUsage?.inputTokens)
263
+ const cachedInputTokens = asNumber(lastUsage?.cached_input_tokens) ?? asNumber(lastUsage?.cachedInputTokens)
264
+ const outputTokens = asNumber(lastUsage?.output_tokens) ?? asNumber(lastUsage?.outputTokens)
265
+ const reasoningOutputTokens =
266
+ asNumber(lastUsage?.reasoning_output_tokens) ?? asNumber(lastUsage?.reasoningOutputTokens)
267
+ const maxTokens = asNumber(usage.model_context_window) ?? asNumber(usage.modelContextWindow)
268
+
269
+ return {
270
+ usedTokens,
271
+ ...(totalProcessedTokens !== undefined && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}),
272
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
273
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
274
+ ...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}),
275
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
276
+ ...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
277
+ ...(inputTokens !== undefined ? { lastInputTokens: inputTokens } : {}),
278
+ ...(cachedInputTokens !== undefined ? { lastCachedInputTokens: cachedInputTokens } : {}),
279
+ ...(outputTokens !== undefined ? { lastOutputTokens: outputTokens } : {}),
280
+ ...(reasoningOutputTokens !== undefined ? { lastReasoningOutputTokens: reasoningOutputTokens } : {}),
281
+ lastUsedTokens: usedTokens,
282
+ compactsAutomatically: true,
283
+ }
284
+ }
285
+
286
+ function todoStatus(status: TurnPlanStep["status"]): TodoItem["status"] {
287
+ if (status === "completed") return "completed"
288
+ if (status === "inProgress") return "in_progress"
289
+ return "pending"
290
+ }
291
+
292
+ function planStepsToTodos(steps: TurnPlanStep[]): TodoItem[] {
293
+ return steps.map((step) => ({
294
+ content: step.step,
295
+ status: todoStatus(step.status),
296
+ activeForm: step.step,
297
+ }))
298
+ }
299
+
300
+ function renderPlanMarkdownFromSteps(steps: TurnPlanStep[]): string {
301
+ return steps.map((step) => {
302
+ const checkbox = step.status === "completed" ? "[x]" : "[ ]"
303
+ return `- ${checkbox} ${step.step}`
304
+ }).join("\n")
305
+ }
306
+
307
+ function dynamicContentToText(contentItems: DynamicToolCallOutputContentItem[] | null | undefined): string {
308
+ if (!contentItems?.length) return ""
309
+ return contentItems
310
+ .map((item) => item.type === "inputText" ? item.text ?? "" : item.imageUrl ?? "")
311
+ .filter(Boolean)
312
+ .join("\n")
313
+ }
314
+
315
+ function dynamicToolPayload(value: Record<string, unknown> | unknown[] | string | number | boolean | null | undefined): Record<string, unknown> {
316
+ const record = asRecord(value)
317
+ if (record) return record
318
+ return { value }
319
+ }
320
+
321
+ function webSearchQuery(item: Extract<ThreadItem, { type: "webSearch" }>): string {
322
+ return item.query || item.action?.query || item.action?.queries?.find((query) => typeof query === "string") || ""
323
+ }
324
+
325
+ function genericDynamicToolCall(toolId: string, toolName: string, input: Record<string, unknown>): TranscriptEntry {
326
+ return timestamped({
327
+ kind: "tool_call",
328
+ tool: {
329
+ kind: "tool",
330
+ toolKind: "unknown_tool",
331
+ toolName,
332
+ toolId,
333
+ input: {
334
+ payload: input,
335
+ },
336
+ rawInput: input,
337
+ },
338
+ })
339
+ }
340
+
341
+ function collabToolCall(item: CollabAgentToolCallItem): TranscriptEntry {
342
+ return timestamped({
343
+ kind: "tool_call",
344
+ tool: {
345
+ kind: "tool",
346
+ toolKind: "subagent_task",
347
+ toolName: "Task",
348
+ toolId: item.id,
349
+ input: {
350
+ subagentType: item.tool,
351
+ },
352
+ rawInput: item as unknown as Record<string, unknown>,
353
+ },
354
+ })
355
+ }
356
+
357
+ function todoToolCall(toolId: string, steps: TurnPlanStep[]): TranscriptEntry {
358
+ return timestamped({
359
+ kind: "tool_call",
360
+ tool: {
361
+ kind: "tool",
362
+ toolKind: "todo_write",
363
+ toolName: "TodoWrite",
364
+ toolId,
365
+ input: {
366
+ todos: planStepsToTodos(steps),
367
+ },
368
+ rawInput: {
369
+ plan: steps,
370
+ },
371
+ },
372
+ })
373
+ }
374
+
375
+ function fileChangeKind(
376
+ kind: "add" | "delete" | "update" | { type: "add" | "delete" | "update"; move_path?: string | null }
377
+ ): { type: "add" | "delete" | "update"; movePath?: string | null } {
378
+ if (typeof kind === "string") {
379
+ return { type: kind }
380
+ }
381
+ return {
382
+ type: kind.type,
383
+ movePath: kind.move_path ?? null,
384
+ }
385
+ }
386
+
387
+ function fileChangeToolId(itemId: string, index: number, totalChanges: number): string {
388
+ if (totalChanges === 1) {
389
+ return itemId
390
+ }
391
+ return `${itemId}:change:${index}`
392
+ }
393
+
394
+ function fileChangePayload(
395
+ item: Extract<ThreadItem, { type: "fileChange" }>,
396
+ change: Extract<ThreadItem, { type: "fileChange" }>["changes"][number]
397
+ ): Record<string, unknown> {
398
+ return {
399
+ ...item,
400
+ changes: [change],
401
+ } as unknown as Record<string, unknown>
402
+ }
403
+
404
+ function parseUnifiedDiff(diff: string): { oldString: string; newString: string } {
405
+ const oldLines: string[] = []
406
+ const newLines: string[] = []
407
+
408
+ for (const line of diff.split(/\r?\n/)) {
409
+ if (!line) continue
410
+ if (line.startsWith("@@") || line.startsWith("---") || line.startsWith("+++")) continue
411
+ if (line === "\") continue
412
+
413
+ const prefix = line[0]
414
+ const content = line.slice(1)
415
+
416
+ if (prefix === " ") {
417
+ oldLines.push(content)
418
+ newLines.push(content)
419
+ continue
420
+ }
421
+ if (prefix === "-") {
422
+ oldLines.push(content)
423
+ continue
424
+ }
425
+ if (prefix === "+") {
426
+ newLines.push(content)
427
+ }
428
+ }
429
+
430
+ return {
431
+ oldString: oldLines.join("\n"),
432
+ newString: newLines.join("\n"),
433
+ }
434
+ }
435
+
436
+ function isUnifiedDiff(diff: string) {
437
+ return diff.includes("@@")
438
+ || diff.startsWith("---")
439
+ || diff.startsWith("+++")
440
+ || diff.split(/\r?\n/).some((line) => (
441
+ line.startsWith("+")
442
+ || line.startsWith("-")
443
+ || line.startsWith(" ")
444
+ || line === "\"
445
+ ))
446
+ }
447
+
448
+ function fileChangeToToolCalls(item: Extract<ThreadItem, { type: "fileChange" }>): TranscriptEntry[] {
449
+ return item.changes.map((change, index) => {
450
+ const payload = fileChangePayload(item, change)
451
+ const toolId = fileChangeToolId(item.id, index, item.changes.length)
452
+ const normalizedKind = fileChangeKind(change.kind)
453
+
454
+ if (normalizedKind.movePath) {
455
+ return timestamped({
456
+ kind: "tool_call",
457
+ tool: {
458
+ kind: "tool",
459
+ toolKind: "unknown_tool",
460
+ toolName: "FileChange",
461
+ toolId,
462
+ input: {
463
+ payload,
464
+ },
465
+ rawInput: payload,
466
+ },
467
+ })
468
+ }
469
+
470
+ if (typeof change.diff === "string") {
471
+ const diffIsUnified = isUnifiedDiff(change.diff)
472
+ const { oldString, newString } = diffIsUnified
473
+ ? parseUnifiedDiff(change.diff)
474
+ : { oldString: change.diff, newString: change.diff }
475
+
476
+ if (normalizedKind.type === "add") {
477
+ return timestamped({
478
+ kind: "tool_call",
479
+ tool: {
480
+ kind: "tool",
481
+ toolKind: "write_file",
482
+ toolName: "Write",
483
+ toolId,
484
+ input: {
485
+ filePath: change.path,
486
+ content: newString,
487
+ },
488
+ rawInput: payload,
489
+ },
490
+ })
491
+ }
492
+
493
+ if (normalizedKind.type === "update") {
494
+ if (!diffIsUnified) {
495
+ return timestamped({
496
+ kind: "tool_call",
497
+ tool: {
498
+ kind: "tool",
499
+ toolKind: "unknown_tool",
500
+ toolName: "FileChange",
501
+ toolId,
502
+ input: {
503
+ payload,
504
+ },
505
+ rawInput: payload,
506
+ },
507
+ })
508
+ }
509
+
510
+ return timestamped({
511
+ kind: "tool_call",
512
+ tool: {
513
+ kind: "tool",
514
+ toolKind: "edit_file",
515
+ toolName: "Edit",
516
+ toolId,
517
+ input: {
518
+ filePath: change.path,
519
+ oldString,
520
+ newString,
521
+ },
522
+ rawInput: payload,
523
+ },
524
+ })
525
+ }
526
+
527
+ if (normalizedKind.type === "delete") {
528
+ return timestamped({
529
+ kind: "tool_call",
530
+ tool: {
531
+ kind: "tool",
532
+ toolKind: "delete_file",
533
+ toolName: "Delete",
534
+ toolId,
535
+ input: {
536
+ filePath: change.path,
537
+ content: oldString,
538
+ },
539
+ rawInput: payload,
540
+ },
541
+ })
542
+ }
543
+ }
544
+
545
+ return timestamped({
546
+ kind: "tool_call",
547
+ tool: {
548
+ kind: "tool",
549
+ toolKind: "unknown_tool",
550
+ toolName: "FileChange",
551
+ toolId,
552
+ input: {
553
+ payload,
554
+ },
555
+ rawInput: payload,
556
+ },
557
+ })
558
+ })
559
+ }
560
+
561
+ function fileChangeToToolResults(item: Extract<ThreadItem, { type: "fileChange" }>): TranscriptEntry[] {
562
+ return item.changes.map((change, index) => timestamped({
563
+ kind: "tool_result",
564
+ toolId: fileChangeToolId(item.id, index, item.changes.length),
565
+ content: fileChangePayload(item, change),
566
+ isError: item.status === "failed" || item.status === "declined",
567
+ }))
568
+ }
569
+
570
+ function itemToToolCalls(item: ThreadItem): TranscriptEntry[] {
571
+ switch (item.type) {
572
+ case "dynamicToolCall":
573
+ return [genericDynamicToolCall(item.id, item.tool, dynamicToolPayload(item.arguments))]
574
+ case "collabAgentToolCall":
575
+ return [collabToolCall(item)]
576
+ case "commandExecution":
577
+ return [timestamped({
578
+ kind: "tool_call",
579
+ tool: {
580
+ kind: "tool",
581
+ toolKind: "bash",
582
+ toolName: "Bash",
583
+ toolId: item.id,
584
+ input: {
585
+ command: item.command,
586
+ },
587
+ rawInput: item,
588
+ },
589
+ })]
590
+ case "webSearch":
591
+ return [timestamped({
592
+ kind: "tool_call",
593
+ tool: {
594
+ kind: "tool",
595
+ toolKind: "web_search",
596
+ toolName: "WebSearch",
597
+ toolId: item.id,
598
+ input: {
599
+ query: webSearchQuery(item),
600
+ },
601
+ rawInput: item,
602
+ },
603
+ })]
604
+ case "mcpToolCall":
605
+ return [timestamped({
606
+ kind: "tool_call",
607
+ tool: {
608
+ kind: "tool",
609
+ toolKind: "mcp_generic",
610
+ toolName: `mcp__${item.server}__${item.tool}`,
611
+ toolId: item.id,
612
+ input: {
613
+ server: item.server,
614
+ tool: item.tool,
615
+ payload: item.arguments ?? {},
616
+ },
617
+ rawInput: item.arguments ?? {},
618
+ },
619
+ })]
620
+ case "fileChange":
621
+ return fileChangeToToolCalls(item)
622
+ case "plan":
623
+ return []
624
+ case "error":
625
+ return [timestamped({
626
+ kind: "tool_call",
627
+ tool: {
628
+ kind: "tool",
629
+ toolKind: "unknown_tool",
630
+ toolName: "Error",
631
+ toolId: item.id,
632
+ input: {
633
+ payload: item as unknown as Record<string, unknown>,
634
+ },
635
+ rawInput: item as unknown as Record<string, unknown>,
636
+ },
637
+ })]
638
+ default:
639
+ return []
640
+ }
641
+ }
642
+
643
+ function itemToToolResults(item: ThreadItem): TranscriptEntry[] {
644
+ switch (item.type) {
645
+ case "dynamicToolCall":
646
+ return [timestamped({
647
+ kind: "tool_result",
648
+ toolId: item.id,
649
+ content: dynamicContentToText(item.contentItems) || item,
650
+ isError: item.status === "failed" || item.success === false,
651
+ })]
652
+ case "collabAgentToolCall":
653
+ return [timestamped({
654
+ kind: "tool_result",
655
+ toolId: item.id,
656
+ content: item,
657
+ isError: item.status === "failed",
658
+ })]
659
+ case "commandExecution":
660
+ return [timestamped({
661
+ kind: "tool_result",
662
+ toolId: item.id,
663
+ content: item.aggregatedOutput ?? item,
664
+ isError: (typeof item.exitCode === "number" && item.exitCode !== 0) || item.status === "failed" || item.status === "declined",
665
+ })]
666
+ case "webSearch":
667
+ return [timestamped({
668
+ kind: "tool_result",
669
+ toolId: item.id,
670
+ content: item,
671
+ })]
672
+ case "mcpToolCall":
673
+ return [timestamped({
674
+ kind: "tool_result",
675
+ toolId: item.id,
676
+ content: contentFromMcpResult(item),
677
+ isError: item.status === "failed",
678
+ })]
679
+ case "fileChange":
680
+ return fileChangeToToolResults(item)
681
+ case "plan":
682
+ return []
683
+ case "error":
684
+ return [timestamped({
685
+ kind: "tool_result",
686
+ toolId: item.id,
687
+ content: item.message,
688
+ isError: true,
689
+ })]
690
+ default:
691
+ return []
692
+ }
693
+ }
694
+
695
+ class AsyncQueue<T> implements AsyncIterable<T> {
696
+ private values: T[] = []
697
+ private resolvers: Array<(value: IteratorResult<T>) => void> = []
698
+ private done = false
699
+
700
+ push(value: T) {
701
+ if (this.done) return
702
+ const resolver = this.resolvers.shift()
703
+ if (resolver) {
704
+ resolver({ value, done: false })
705
+ return
706
+ }
707
+ this.values.push(value)
708
+ }
709
+
710
+ finish() {
711
+ if (this.done) return
712
+ this.done = true
713
+ while (this.resolvers.length > 0) {
714
+ const resolver = this.resolvers.shift()
715
+ resolver?.({ value: undefined as T, done: true })
716
+ }
717
+ }
718
+
719
+ [Symbol.asyncIterator](): AsyncIterator<T> {
720
+ return {
721
+ next: () => {
722
+ if (this.values.length > 0) {
723
+ return Promise.resolve({ value: this.values.shift() as T, done: false })
724
+ }
725
+ if (this.done) {
726
+ return Promise.resolve({ value: undefined as T, done: true })
727
+ }
728
+ return new Promise<IteratorResult<T>>((resolve) => {
729
+ this.resolvers.push(resolve)
730
+ })
731
+ },
732
+ }
733
+ }
734
+ }
735
+
736
+ export class CodexAppServerManager {
737
+ private readonly sessions = new Map<string, SessionContext>()
738
+ private readonly spawnProcess: SpawnCodexAppServer
739
+
740
+ constructor(args: { spawnProcess?: SpawnCodexAppServer } = {}) {
741
+ this.spawnProcess = args.spawnProcess ?? ((cwd) =>
742
+ spawn("codex", ["app-server"], {
743
+ cwd,
744
+ stdio: ["pipe", "pipe", "pipe"],
745
+ env: process.env,
746
+ }) as unknown as CodexAppServerProcess)
747
+ }
748
+
749
+ async startSession(args: StartCodexSessionArgs) {
750
+ const existing = this.sessions.get(args.chatId)
751
+ if (existing && !existing.closed && existing.cwd === args.cwd && !args.pendingForkSessionToken) {
752
+ return
753
+ }
754
+
755
+ if (existing) {
756
+ this.stopSession(args.chatId)
757
+ }
758
+
759
+ const child = this.spawnProcess(args.cwd)
760
+ const context: SessionContext = {
761
+ chatId: args.chatId,
762
+ cwd: args.cwd,
763
+ child,
764
+ pendingRequests: new Map(),
765
+ pendingTurn: null,
766
+ sessionToken: null,
767
+ stderrLines: [],
768
+ closed: false,
769
+ }
770
+ this.sessions.set(args.chatId, context)
771
+ this.attachListeners(context)
772
+
773
+ await this.sendRequest(context, "initialize", {
774
+ clientInfo: {
775
+ name: "kanna_desktop",
776
+ title: "Kanna",
777
+ version: "0.1.0",
778
+ },
779
+ capabilities: {
780
+ experimentalApi: true,
781
+ },
782
+ } satisfies InitializeParams)
783
+ this.writeMessage(context, {
784
+ method: "initialized",
785
+ })
786
+
787
+ const threadParams = {
788
+ model: args.model,
789
+ cwd: args.cwd,
790
+ serviceTier: args.serviceTier,
791
+ approvalPolicy: "never",
792
+ sandbox: "danger-full-access",
793
+ experimentalRawEvents: false,
794
+ persistExtendedHistory: false,
795
+ } satisfies ThreadStartParams
796
+
797
+ let response: ThreadStartResponse | ThreadResumeResponse | ThreadForkResponse
798
+ if (args.pendingForkSessionToken) {
799
+ response = await this.sendRequest<ThreadForkResponse>(context, "thread/fork", {
800
+ threadId: args.pendingForkSessionToken,
801
+ model: args.model,
802
+ cwd: args.cwd,
803
+ serviceTier: args.serviceTier,
804
+ approvalPolicy: "never",
805
+ sandbox: "danger-full-access",
806
+ persistExtendedHistory: false,
807
+ } satisfies ThreadForkParams)
808
+ } else if (args.sessionToken) {
809
+ try {
810
+ response = await this.sendRequest<ThreadResumeResponse>(context, "thread/resume", {
811
+ threadId: args.sessionToken,
812
+ model: args.model,
813
+ cwd: args.cwd,
814
+ serviceTier: args.serviceTier,
815
+ approvalPolicy: "never",
816
+ sandbox: "danger-full-access",
817
+ persistExtendedHistory: false,
818
+ } satisfies ThreadResumeParams)
819
+ } catch (error) {
820
+ if (!isRecoverableResumeError(error)) {
821
+ this.stopSession(args.chatId)
822
+ throw error
823
+ }
824
+ response = await this.sendRequest<ThreadStartResponse>(context, "thread/start", threadParams)
825
+ }
826
+ } else {
827
+ response = await this.sendRequest<ThreadStartResponse>(context, "thread/start", threadParams)
828
+ }
829
+
830
+ context.sessionToken = response.thread.id
831
+ return context.sessionToken
832
+ }
833
+
834
+ async startTurn(args: StartCodexTurnArgs): Promise<HarnessTurn> {
835
+ const context = this.requireSession(args.chatId)
836
+ if (context.pendingTurn) {
837
+ throw new Error("Codex turn is already running")
838
+ }
839
+
840
+ const queue = new AsyncQueue<HarnessEvent>()
841
+ if (context.sessionToken) {
842
+ queue.push({ type: "session_token", sessionToken: context.sessionToken })
843
+ }
844
+ queue.push({ type: "transcript", entry: codexSystemInitEntry(args.model) })
845
+
846
+ const pendingTurn: PendingTurn = {
847
+ turnId: null,
848
+ model: args.model,
849
+ planMode: args.planMode,
850
+ queue,
851
+ startedToolIds: new Set(),
852
+ handledDynamicToolIds: new Set(),
853
+ latestPlanExplanation: null,
854
+ latestPlanSteps: [],
855
+ latestPlanText: null,
856
+ planTextByItemId: new Map(),
857
+ todoSequence: 0,
858
+ pendingWebSearchResultToolId: null,
859
+ resolved: false,
860
+ onToolRequest: args.onToolRequest,
861
+ onApprovalRequest: args.onApprovalRequest,
862
+ }
863
+ context.pendingTurn = pendingTurn
864
+
865
+ try {
866
+ const response = await this.sendRequest<TurnStartResponse>(context, "turn/start", {
867
+ threadId: context.sessionToken ?? "",
868
+ input: [
869
+ {
870
+ type: "text",
871
+ text: args.content,
872
+ text_elements: [],
873
+ },
874
+ ],
875
+ approvalPolicy: "never",
876
+ model: args.model,
877
+ effort: args.effort,
878
+ serviceTier: args.serviceTier,
879
+ collaborationMode: {
880
+ mode: args.planMode ? "plan" : "default",
881
+ settings: {
882
+ model: args.model,
883
+ reasoning_effort: null,
884
+ developer_instructions: null,
885
+ },
886
+ },
887
+ } satisfies TurnStartParams)
888
+ if (context.pendingTurn) {
889
+ context.pendingTurn.turnId = response.turn.id
890
+ } else {
891
+ pendingTurn.turnId = response.turn.id
892
+ }
893
+ } catch (error) {
894
+ context.pendingTurn = null
895
+ queue.finish()
896
+ throw error
897
+ }
898
+
899
+ return {
900
+ provider: "codex",
901
+ stream: queue,
902
+ interrupt: async () => {
903
+ const pendingTurn = context.pendingTurn
904
+ if (!pendingTurn) return
905
+
906
+ context.pendingTurn = null
907
+ pendingTurn.resolved = true
908
+ pendingTurn.queue.finish()
909
+
910
+ if (!pendingTurn.turnId || !context.sessionToken) return
911
+
912
+ await this.sendRequest(context, "turn/interrupt", {
913
+ threadId: context.sessionToken,
914
+ turnId: pendingTurn.turnId,
915
+ } satisfies TurnInterruptParams)
916
+ },
917
+ close: () => {},
918
+ }
919
+ }
920
+
921
+ async generateStructured(args: GenerateStructuredArgs): Promise<string | null> {
922
+ const chatId = `quick-${randomUUID()}`
923
+ let turn: HarnessTurn | null = null
924
+ let assistantText = ""
925
+ let resultText = ""
926
+
927
+ try {
928
+ await this.startSession({
929
+ chatId,
930
+ cwd: args.cwd,
931
+ model: args.model ?? "gpt-5.5",
932
+ serviceTier: args.serviceTier ?? "fast",
933
+ sessionToken: null,
934
+ })
935
+
936
+ turn = await this.startTurn({
937
+ chatId,
938
+ model: args.model ?? "gpt-5.5",
939
+ effort: args.effort,
940
+ serviceTier: args.serviceTier ?? "fast",
941
+ content: args.prompt,
942
+ planMode: false,
943
+ onToolRequest: async () => ({}),
944
+ })
945
+
946
+ for await (const event of turn.stream) {
947
+ if (event.type !== "transcript" || !event.entry) continue
948
+ if (event.entry.kind === "assistant_text") {
949
+ assistantText += assistantText ? `\n${event.entry.text}` : event.entry.text
950
+ }
951
+ if (event.entry.kind === "result" && !event.entry.isError && event.entry.result.trim()) {
952
+ resultText = event.entry.result
953
+ }
954
+ }
955
+
956
+ const candidate = assistantText.trim() || resultText.trim()
957
+ return candidate || null
958
+ } finally {
959
+ turn?.close()
960
+ this.stopSession(chatId)
961
+ }
962
+ }
963
+
964
+ stopSession(chatId: string) {
965
+ const context = this.sessions.get(chatId)
966
+ if (!context) return
967
+ context.closed = true
968
+ context.pendingTurn?.queue.finish()
969
+ this.sessions.delete(chatId)
970
+ try {
971
+ context.child.kill("SIGKILL")
972
+ } catch {
973
+ // ignore kill failures
974
+ }
975
+ }
976
+
977
+ stopAll() {
978
+ for (const chatId of this.sessions.keys()) {
979
+ this.stopSession(chatId)
980
+ }
981
+ }
982
+
983
+ private requireSession(chatId: string) {
984
+ const context = this.sessions.get(chatId)
985
+ if (!context || context.closed) {
986
+ throw new Error("Codex session not started")
987
+ }
988
+ return context
989
+ }
990
+
991
+ private attachListeners(context: SessionContext) {
992
+ const lines = createInterface({ input: context.child.stdout })
993
+ void (async () => {
994
+ for await (const line of lines) {
995
+ const parsed = parseJsonLine(line)
996
+ if (!parsed) continue
997
+
998
+ if (isJsonRpcResponse(parsed)) {
999
+ this.handleResponse(context, parsed)
1000
+ continue
1001
+ }
1002
+
1003
+ if (isServerRequest(parsed)) {
1004
+ void this.handleServerRequest(context, parsed)
1005
+ continue
1006
+ }
1007
+
1008
+ if (isServerNotification(parsed)) {
1009
+ void this.handleNotification(context, parsed)
1010
+ }
1011
+ }
1012
+ })()
1013
+
1014
+ const stderr = createInterface({ input: context.child.stderr })
1015
+ void (async () => {
1016
+ for await (const line of stderr) {
1017
+ if (line.trim()) {
1018
+ context.stderrLines.push(line.trim())
1019
+ }
1020
+ }
1021
+ })()
1022
+
1023
+ context.child.on("error", (error) => {
1024
+ this.failContext(context, error.message)
1025
+ })
1026
+
1027
+ context.child.on("close", (code) => {
1028
+ if (context.closed) return
1029
+ queueMicrotask(() => {
1030
+ if (context.closed) return
1031
+ const message = context.stderrLines.at(-1) || `Codex app-server exited with code ${code ?? 1}`
1032
+ this.failContext(context, message)
1033
+ })
1034
+ })
1035
+ }
1036
+
1037
+ private handleResponse(context: SessionContext, response: JsonRpcResponse) {
1038
+ const pending = context.pendingRequests.get(response.id)
1039
+ if (!pending) return
1040
+ context.pendingRequests.delete(response.id)
1041
+ if (response.error) {
1042
+ pending.reject(new Error(`${pending.method} failed: ${response.error.message ?? "Unknown error"}`))
1043
+ return
1044
+ }
1045
+ pending.resolve(response.result)
1046
+ }
1047
+
1048
+ private async handleServerRequest(context: SessionContext, request: ServerRequest) {
1049
+ const pendingTurn = context.pendingTurn
1050
+ if (!pendingTurn) {
1051
+ this.writeMessage(context, {
1052
+ id: request.id,
1053
+ error: {
1054
+ message: "No active turn",
1055
+ },
1056
+ })
1057
+ return
1058
+ }
1059
+
1060
+ if (request.method === "item/tool/requestUserInput") {
1061
+ const questions = toAskUserQuestionItems(request.params)
1062
+ const toolId = request.params.itemId
1063
+ const toolRequest: HarnessToolRequest = {
1064
+ tool: {
1065
+ kind: "tool",
1066
+ toolKind: "ask_user_question",
1067
+ toolName: "AskUserQuestion",
1068
+ toolId,
1069
+ input: { questions },
1070
+ rawInput: {
1071
+ questions: request.params.questions,
1072
+ },
1073
+ },
1074
+ }
1075
+ pendingTurn.queue.push({
1076
+ type: "transcript",
1077
+ entry: timestamped({
1078
+ kind: "tool_call",
1079
+ tool: toolRequest.tool,
1080
+ }),
1081
+ })
1082
+
1083
+ const result = await pendingTurn.onToolRequest(toolRequest)
1084
+ this.writeMessage(context, {
1085
+ id: request.id,
1086
+ result: toToolRequestUserInputResponse(result, request.params.questions),
1087
+ })
1088
+ return
1089
+ }
1090
+
1091
+ if (request.method === "item/tool/call") {
1092
+ pendingTurn.handledDynamicToolIds.add(request.params.callId)
1093
+ if (request.params.tool === "update_plan") {
1094
+ const args = asRecord(request.params.arguments)
1095
+ const plan = Array.isArray(args?.plan) ? args.plan : []
1096
+ const steps: TurnPlanStep[] = plan
1097
+ .map((entry) => asRecord(entry))
1098
+ .filter((entry): entry is Record<string, unknown> => Boolean(entry))
1099
+ .map((entry) => {
1100
+ const status: TurnPlanStep["status"] =
1101
+ entry.status === "completed"
1102
+ ? "completed"
1103
+ : entry.status === "inProgress" || entry.status === "in_progress"
1104
+ ? "inProgress"
1105
+ : "pending"
1106
+ return {
1107
+ step: typeof entry.step === "string" ? entry.step : "",
1108
+ status,
1109
+ }
1110
+ })
1111
+ .filter((step) => step.step.length > 0)
1112
+
1113
+ if (steps.length > 0) {
1114
+ pendingTurn.latestPlanSteps = steps
1115
+ pendingTurn.latestPlanExplanation = typeof args?.explanation === "string" ? args.explanation : pendingTurn.latestPlanExplanation
1116
+ pendingTurn.queue.push({
1117
+ type: "transcript",
1118
+ entry: todoToolCall(request.params.callId, steps),
1119
+ })
1120
+ pendingTurn.queue.push({
1121
+ type: "transcript",
1122
+ entry: timestamped({
1123
+ kind: "tool_result",
1124
+ toolId: request.params.callId,
1125
+ content: "",
1126
+ }),
1127
+ })
1128
+ }
1129
+
1130
+ this.writeMessage(context, {
1131
+ id: request.id,
1132
+ result: {
1133
+ contentItems: [],
1134
+ success: true,
1135
+ } satisfies DynamicToolCallResponse,
1136
+ })
1137
+ return
1138
+ }
1139
+
1140
+ const payload = dynamicToolPayload(request.params.arguments)
1141
+ pendingTurn.queue.push({
1142
+ type: "transcript",
1143
+ entry: genericDynamicToolCall(request.params.callId, request.params.tool, payload),
1144
+ })
1145
+ const errorMessage = `Unsupported dynamic tool call: ${request.params.tool}`
1146
+ pendingTurn.queue.push({
1147
+ type: "transcript",
1148
+ entry: timestamped({
1149
+ kind: "tool_result",
1150
+ toolId: request.params.callId,
1151
+ content: errorMessage,
1152
+ isError: true,
1153
+ }),
1154
+ })
1155
+ this.writeMessage(context, {
1156
+ id: request.id,
1157
+ result: {
1158
+ contentItems: [{ type: "inputText", text: errorMessage }],
1159
+ success: false,
1160
+ } satisfies DynamicToolCallResponse,
1161
+ })
1162
+ return
1163
+ }
1164
+
1165
+ if (request.method === "item/commandExecution/requestApproval") {
1166
+ const decision = await pendingTurn.onApprovalRequest?.({
1167
+ requestId: request.id,
1168
+ kind: "command_execution",
1169
+ params: request.params,
1170
+ }) ?? "decline"
1171
+ this.writeMessage(context, {
1172
+ id: request.id,
1173
+ result: {
1174
+ decision,
1175
+ } satisfies CommandExecutionRequestApprovalResponse,
1176
+ })
1177
+ return
1178
+ }
1179
+
1180
+ const decision = await pendingTurn.onApprovalRequest?.({
1181
+ requestId: request.id,
1182
+ kind: "file_change",
1183
+ params: request.params,
1184
+ }) ?? "decline"
1185
+ this.writeMessage(context, {
1186
+ id: request.id,
1187
+ result: {
1188
+ decision,
1189
+ } satisfies FileChangeRequestApprovalResponse,
1190
+ })
1191
+ }
1192
+
1193
+ private async handleNotification(context: SessionContext, notification: ServerNotification) {
1194
+ if (notification.method === "thread/started") {
1195
+ context.sessionToken = notification.params.thread.id
1196
+ if (context.pendingTurn) {
1197
+ context.pendingTurn.queue.push({
1198
+ type: "session_token",
1199
+ sessionToken: notification.params.thread.id,
1200
+ })
1201
+ }
1202
+ return
1203
+ }
1204
+
1205
+ const pendingTurn = context.pendingTurn
1206
+ if (!pendingTurn) return
1207
+
1208
+ switch (notification.method) {
1209
+ case "thread/tokenUsage/updated":
1210
+ this.handleTokenUsageUpdated(pendingTurn, notification.params)
1211
+ return
1212
+ case "turn/plan/updated":
1213
+ this.handlePlanUpdated(pendingTurn, notification.params)
1214
+ return
1215
+ case "item/started":
1216
+ this.handleItemStarted(pendingTurn, notification.params)
1217
+ return
1218
+ case "item/completed":
1219
+ this.handleItemCompleted(pendingTurn, notification.params)
1220
+ return
1221
+ case "item/plan/delta":
1222
+ this.handlePlanDelta(pendingTurn, notification.params)
1223
+ return
1224
+ case "turn/completed":
1225
+ await this.handleTurnCompleted(context, notification.params)
1226
+ return
1227
+ case "thread/compacted":
1228
+ this.handleContextCompacted(pendingTurn, notification.params)
1229
+ return
1230
+ case "error":
1231
+ this.failContext(context, notification.params.error.message)
1232
+ return
1233
+ default:
1234
+ return
1235
+ }
1236
+ }
1237
+
1238
+ private handleItemStarted(pendingTurn: PendingTurn, notification: ItemStartedNotification) {
1239
+ if (notification.item.type === "plan") {
1240
+ pendingTurn.planTextByItemId.set(notification.item.id, notification.item.text)
1241
+ pendingTurn.latestPlanText = notification.item.text
1242
+ return
1243
+ }
1244
+
1245
+ if (
1246
+ notification.item.type === "commandExecution"
1247
+ || notification.item.type === "webSearch"
1248
+ || notification.item.type === "mcpToolCall"
1249
+ || notification.item.type === "dynamicToolCall"
1250
+ || notification.item.type === "collabAgentToolCall"
1251
+ || notification.item.type === "fileChange"
1252
+ || notification.item.type === "error"
1253
+ ) {
1254
+ if (pendingTurn.handledDynamicToolIds.has(notification.item.id)) {
1255
+ return
1256
+ }
1257
+ if (notification.item.type === "webSearch" && !webSearchQuery(notification.item)) {
1258
+ return
1259
+ }
1260
+ }
1261
+
1262
+ const entries = itemToToolCalls(notification.item)
1263
+ for (const entry of entries) {
1264
+ if (entry.kind === "tool_call") {
1265
+ pendingTurn.startedToolIds.add(entry.tool.toolId)
1266
+ }
1267
+ pendingTurn.queue.push({ type: "transcript", entry })
1268
+ }
1269
+ }
1270
+
1271
+ private handleItemCompleted(pendingTurn: PendingTurn, notification: ItemCompletedNotification) {
1272
+ if (notification.item.type === "agentMessage") {
1273
+ pendingTurn.queue.push({
1274
+ type: "transcript",
1275
+ entry: timestamped({
1276
+ kind: "assistant_text",
1277
+ text: notification.item.text,
1278
+ }),
1279
+ })
1280
+ if (pendingTurn.pendingWebSearchResultToolId && notification.item.text.trim()) {
1281
+ pendingTurn.queue.push({
1282
+ type: "transcript",
1283
+ entry: timestamped({
1284
+ kind: "tool_result",
1285
+ toolId: pendingTurn.pendingWebSearchResultToolId,
1286
+ content: notification.item.text,
1287
+ }),
1288
+ })
1289
+ pendingTurn.pendingWebSearchResultToolId = null
1290
+ }
1291
+ return
1292
+ }
1293
+
1294
+ if (notification.item.type === "plan") {
1295
+ pendingTurn.planTextByItemId.set(notification.item.id, notification.item.text)
1296
+ pendingTurn.latestPlanText = notification.item.text
1297
+ return
1298
+ }
1299
+
1300
+ if (pendingTurn.handledDynamicToolIds.has(notification.item.id)) {
1301
+ return
1302
+ }
1303
+
1304
+ const startedEntries = itemToToolCalls(notification.item)
1305
+ for (const entry of startedEntries) {
1306
+ if (entry.kind !== "tool_call") {
1307
+ continue
1308
+ }
1309
+ if (pendingTurn.startedToolIds.has(entry.tool.toolId)) {
1310
+ continue
1311
+ }
1312
+ pendingTurn.startedToolIds.add(entry.tool.toolId)
1313
+ pendingTurn.queue.push({ type: "transcript", entry })
1314
+ }
1315
+
1316
+ const resultEntries = itemToToolResults(notification.item)
1317
+ for (const entry of resultEntries) {
1318
+ pendingTurn.queue.push({ type: "transcript", entry })
1319
+ if (notification.item.type === "webSearch" && entry.kind === "tool_result" && !entry.isError) {
1320
+ pendingTurn.pendingWebSearchResultToolId = notification.item.id
1321
+ }
1322
+ }
1323
+ }
1324
+
1325
+ private handlePlanUpdated(pendingTurn: PendingTurn, notification: TurnPlanUpdatedNotification) {
1326
+ pendingTurn.latestPlanExplanation = notification.explanation ?? null
1327
+ pendingTurn.latestPlanSteps = notification.plan
1328
+ if (notification.plan.length === 0) {
1329
+ return
1330
+ }
1331
+ pendingTurn.todoSequence += 1
1332
+ pendingTurn.queue.push({
1333
+ type: "transcript",
1334
+ entry: todoToolCall(
1335
+ `${notification.turnId}:todo-${pendingTurn.todoSequence}`,
1336
+ notification.plan
1337
+ ),
1338
+ })
1339
+ }
1340
+
1341
+ private handlePlanDelta(pendingTurn: PendingTurn, notification: PlanDeltaNotification) {
1342
+ const current = pendingTurn.planTextByItemId.get(notification.itemId) ?? ""
1343
+ const next = `${current}${notification.delta}`
1344
+ pendingTurn.planTextByItemId.set(notification.itemId, next)
1345
+ pendingTurn.latestPlanText = next
1346
+ }
1347
+
1348
+ private handleContextCompacted(pendingTurn: PendingTurn, _notification: ContextCompactedNotification) {
1349
+ pendingTurn.queue.push({
1350
+ type: "transcript",
1351
+ entry: timestamped({ kind: "compact_boundary" }),
1352
+ })
1353
+ }
1354
+
1355
+ private handleTokenUsageUpdated(
1356
+ pendingTurn: PendingTurn,
1357
+ notification: ThreadTokenUsageUpdatedNotification,
1358
+ ) {
1359
+ const usage = normalizeCodexTokenUsage(notification)
1360
+ if (!usage) {
1361
+ return
1362
+ }
1363
+
1364
+ pendingTurn.queue.push({
1365
+ type: "transcript",
1366
+ entry: timestamped({
1367
+ kind: "context_window_updated",
1368
+ usage,
1369
+ }),
1370
+ })
1371
+ }
1372
+
1373
+ private async handleTurnCompleted(context: SessionContext, notification: TurnCompletedNotification) {
1374
+ const pendingTurn = context.pendingTurn
1375
+ if (!pendingTurn) return
1376
+ const status = notification.turn.status
1377
+ const isCancelled = status === "interrupted"
1378
+ const isError = status === "failed"
1379
+ pendingTurn.pendingWebSearchResultToolId = null
1380
+
1381
+ if (!isCancelled && !isError && pendingTurn.planMode) {
1382
+ const planText = pendingTurn.latestPlanText?.trim()
1383
+ || renderPlanMarkdownFromSteps(pendingTurn.latestPlanSteps).trim()
1384
+
1385
+ if (planText) {
1386
+ pendingTurn.turnId = null
1387
+ const tool = {
1388
+ kind: "tool" as const,
1389
+ toolKind: "exit_plan_mode" as const,
1390
+ toolName: "ExitPlanMode",
1391
+ toolId: `${notification.turn.id}:exit-plan`,
1392
+ input: {
1393
+ plan: planText,
1394
+ summary: pendingTurn.latestPlanExplanation ?? undefined,
1395
+ },
1396
+ rawInput: {
1397
+ plan: planText,
1398
+ summary: pendingTurn.latestPlanExplanation ?? undefined,
1399
+ },
1400
+ }
1401
+ pendingTurn.queue.push({
1402
+ type: "transcript",
1403
+ entry: timestamped({
1404
+ kind: "tool_call",
1405
+ tool,
1406
+ }),
1407
+ })
1408
+ await pendingTurn.onToolRequest({ tool })
1409
+ pendingTurn.resolved = true
1410
+ pendingTurn.queue.finish()
1411
+ context.pendingTurn = null
1412
+ return
1413
+ }
1414
+ }
1415
+
1416
+ pendingTurn.resolved = true
1417
+ pendingTurn.queue.push({
1418
+ type: "transcript",
1419
+ entry: timestamped({
1420
+ kind: "result",
1421
+ subtype: isCancelled ? "cancelled" : isError ? "error" : "success",
1422
+ isError,
1423
+ durationMs: 0,
1424
+ result: notification.turn.error?.message ?? "",
1425
+ }),
1426
+ })
1427
+ pendingTurn.queue.finish()
1428
+ context.pendingTurn = null
1429
+ }
1430
+
1431
+ private failContext(context: SessionContext, message: string) {
1432
+ const pendingTurn = context.pendingTurn
1433
+ if (pendingTurn && !pendingTurn.resolved) {
1434
+ pendingTurn.queue.push({
1435
+ type: "transcript",
1436
+ entry: timestamped({
1437
+ kind: "result",
1438
+ subtype: "error",
1439
+ isError: true,
1440
+ durationMs: 0,
1441
+ result: message,
1442
+ }),
1443
+ })
1444
+ pendingTurn.queue.finish()
1445
+ context.pendingTurn = null
1446
+ }
1447
+
1448
+ for (const pending of context.pendingRequests.values()) {
1449
+ pending.reject(new Error(message))
1450
+ }
1451
+ context.pendingRequests.clear()
1452
+ context.closed = true
1453
+ }
1454
+
1455
+ private async sendRequest<TResult>(context: SessionContext, method: string, params: unknown): Promise<TResult> {
1456
+ const id = randomUUID()
1457
+ const promise = new Promise<TResult>((resolve, reject) => {
1458
+ context.pendingRequests.set(id, {
1459
+ method,
1460
+ resolve: resolve as (value: unknown) => void,
1461
+ reject,
1462
+ })
1463
+ })
1464
+ this.writeMessage(context, {
1465
+ id,
1466
+ method,
1467
+ params,
1468
+ })
1469
+ return await promise
1470
+ }
1471
+
1472
+ private writeMessage(context: SessionContext, message: Record<string, unknown>) {
1473
+ context.child.stdin.write(`${JSON.stringify(message)}\n`)
1474
+ }
1475
+ }