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