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,1448 @@
1
+ import { existsSync, readFileSync as readFileSyncImmediate } from 'node:fs';
2
+ import { appendFile, chmod, mkdir, open, readdir, rm } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { getDataDir, LOG_PREFIX } from 'src/shared/branding';
6
+ import type {
7
+ AgentProvider,
8
+ SessionHistoryPage,
9
+ SessionHistorySnapshot,
10
+ TranscriptEntry,
11
+ WorkspaceDiffFile,
12
+ WorkspacePullRequestSummary,
13
+ WorkspaceReviewState,
14
+ WorkspaceVisibilityState,
15
+ } from 'src/shared/types';
16
+ import { acquireDataDirLock, type DataDirLock } from './data-dir-lock';
17
+ import { atomicWriteFile, quarantineFile, readTextIfExists } from './durable-file';
18
+ import {
19
+ cloneTranscriptEntries,
20
+ createEmptyState,
21
+ type DirectoryEvent,
22
+ type QueuedSessionMessageRecord,
23
+ type QueuedSessionSendPayload,
24
+ type QueueEvent,
25
+ type SessionEvent,
26
+ type SnapshotFile,
27
+ type StoreEvent,
28
+ type StoreState,
29
+ type TurnEvent,
30
+ type WorkspaceEvent,
31
+ } from './event';
32
+ import { resolveLocalPath } from './paths';
33
+
34
+ const COMPACTION_THRESHOLD_BYTES = 2 * 1024 * 1024;
35
+ const SESSION_MESSAGE_PREVIEW_MAX_LENGTH = 140;
36
+ export const MAX_QUEUED_SESSION_MESSAGES = 25;
37
+
38
+ function workspaceDiffFilesSignature(files: WorkspaceDiffFile[] | undefined) {
39
+ return JSON.stringify(
40
+ (files ?? [])
41
+ .map((file) => ({
42
+ path: file.path,
43
+ changeType: file.changeType,
44
+ isUntracked: file.isUntracked,
45
+ additions: file.additions,
46
+ deletions: file.deletions,
47
+ patchDigest: file.patchDigest,
48
+ mimeType: file.mimeType,
49
+ size: file.size,
50
+ }))
51
+ .sort((left, right) => left.path.localeCompare(right.path)),
52
+ );
53
+ }
54
+
55
+ function pullRequestCommentsSignature(comments: WorkspacePullRequestSummary['comments']) {
56
+ return JSON.stringify(
57
+ (comments ?? [])
58
+ .map((comment) => ({
59
+ id: comment.id,
60
+ author: comment.author,
61
+ authorAssociation: comment.authorAssociation,
62
+ body: comment.body,
63
+ url: comment.url,
64
+ path: comment.path,
65
+ line: comment.line,
66
+ isResolved: comment.isResolved,
67
+ isBot: comment.isBot,
68
+ source: comment.source,
69
+ createdAt: comment.createdAt,
70
+ updatedAt: comment.updatedAt,
71
+ }))
72
+ .sort((left, right) => left.id.localeCompare(right.id)),
73
+ );
74
+ }
75
+
76
+ function pullRequestChecksSignature(checks: WorkspacePullRequestSummary['checks']) {
77
+ return JSON.stringify(
78
+ (checks ?? [])
79
+ .map((check) => ({
80
+ name: check.name,
81
+ workflowName: check.workflowName,
82
+ status: check.status,
83
+ conclusion: check.conclusion,
84
+ detailsUrl: check.detailsUrl,
85
+ startedAt: check.startedAt,
86
+ completedAt: check.completedAt,
87
+ summary: check.summary,
88
+ canFetchLogs: check.canFetchLogs,
89
+ }))
90
+ .sort((left, right) =>
91
+ [
92
+ left.workflowName ?? '',
93
+ left.name,
94
+ left.detailsUrl ?? '',
95
+ left.startedAt ?? '',
96
+ left.completedAt ?? '',
97
+ left.conclusion ?? '',
98
+ ]
99
+ .join('\0')
100
+ .localeCompare(
101
+ [
102
+ right.workflowName ?? '',
103
+ right.name,
104
+ right.detailsUrl ?? '',
105
+ right.startedAt ?? '',
106
+ right.completedAt ?? '',
107
+ right.conclusion ?? '',
108
+ ].join('\0'),
109
+ ),
110
+ ),
111
+ );
112
+ }
113
+
114
+ function pullRequestFilesEqual(
115
+ left: WorkspacePullRequestSummary['files'],
116
+ right: WorkspacePullRequestSummary['files'],
117
+ ) {
118
+ return workspaceDiffFilesSignature(left) === workspaceDiffFilesSignature(right);
119
+ }
120
+
121
+ function pullRequestCommentsEqual(
122
+ left: WorkspacePullRequestSummary['comments'],
123
+ right: WorkspacePullRequestSummary['comments'],
124
+ ) {
125
+ return pullRequestCommentsSignature(left) === pullRequestCommentsSignature(right);
126
+ }
127
+
128
+ function pullRequestChecksEqual(
129
+ left: WorkspacePullRequestSummary['checks'],
130
+ right: WorkspacePullRequestSummary['checks'],
131
+ ) {
132
+ return pullRequestChecksSignature(left) === pullRequestChecksSignature(right);
133
+ }
134
+
135
+ interface TranscriptPageResult {
136
+ entries: TranscriptEntry[];
137
+ hasOlder: boolean;
138
+ olderCursor: string | null;
139
+ }
140
+
141
+ function encodeHistoryCursor(index: number) {
142
+ return `idx:${index}`;
143
+ }
144
+
145
+ function decodeCursor(cursor: string) {
146
+ if (cursor.startsWith('idx:')) {
147
+ const value = Number.parseInt(cursor.slice('idx:'.length), 10);
148
+ if (!Number.isInteger(value) || value < 0) {
149
+ throw new Error('Invalid history cursor');
150
+ }
151
+ return value;
152
+ }
153
+ throw new Error('Invalid history cursor');
154
+ }
155
+
156
+ function getHistorySnapshot(
157
+ page: TranscriptPageResult,
158
+ recentLimit: number,
159
+ ): SessionHistorySnapshot {
160
+ return {
161
+ hasOlder: page.hasOlder,
162
+ olderCursor: page.olderCursor,
163
+ recentLimit,
164
+ };
165
+ }
166
+
167
+ function messagePreview(content: string) {
168
+ const collapsed = content.replace(/\s+/g, ' ').trim();
169
+ if (collapsed.length <= SESSION_MESSAGE_PREVIEW_MAX_LENGTH) return collapsed;
170
+ return `${collapsed.slice(0, SESSION_MESSAGE_PREVIEW_MAX_LENGTH - 1).trimEnd()}…`;
171
+ }
172
+
173
+ interface EventStoreOptions {
174
+ lockDataDir?: boolean;
175
+ }
176
+
177
+ export class EventStore {
178
+ readonly dataDir: string;
179
+ readonly state: StoreState = createEmptyState();
180
+ private writeChain = Promise.resolve();
181
+ private readonly snapshotPath: string;
182
+ private readonly snapshotBackupPath: string;
183
+ private readonly directoriesLogPath: string;
184
+ private readonly workspacesLogPath: string;
185
+ private readonly sessionsLogPath: string;
186
+ private readonly turnsLogPath: string;
187
+ private readonly queuesLogPath: string;
188
+ private readonly transcriptsDir: string;
189
+ private readonly lockDataDir: boolean;
190
+ private dataDirLock: DataDirLock | null = null;
191
+ private cachedTranscript: { sessionId: string; entries: TranscriptEntry[] } | null = null;
192
+ private nextQueueSequence = 1;
193
+
194
+ constructor(dataDir = getDataDir(homedir()), options: EventStoreOptions = {}) {
195
+ this.dataDir = dataDir;
196
+ this.lockDataDir = options.lockDataDir ?? false;
197
+ this.snapshotPath = path.join(this.dataDir, 'snapshot.json');
198
+ this.snapshotBackupPath = path.join(this.dataDir, 'snapshot.previous.json');
199
+ this.directoriesLogPath = path.join(this.dataDir, 'directories.jsonl');
200
+ this.workspacesLogPath = path.join(this.dataDir, 'workspaces.jsonl');
201
+ this.sessionsLogPath = path.join(this.dataDir, 'sessions.jsonl');
202
+ this.turnsLogPath = path.join(this.dataDir, 'turns.jsonl');
203
+ this.queuesLogPath = path.join(this.dataDir, 'queues.jsonl');
204
+ this.transcriptsDir = path.join(this.dataDir, 'transcripts');
205
+ }
206
+
207
+ private normalizeQueuedMessage(message: QueuedSessionMessageRecord) {
208
+ this.nextQueueSequence = Math.max(this.nextQueueSequence, message.sequence + 1);
209
+ return structuredClone(message);
210
+ }
211
+
212
+ async initialize() {
213
+ if (this.lockDataDir) {
214
+ this.dataDirLock = await acquireDataDirLock(this.dataDir);
215
+ }
216
+
217
+ try {
218
+ await mkdir(this.dataDir, { recursive: true, mode: 0o700 });
219
+ await chmod(this.dataDir, 0o700);
220
+ await mkdir(this.transcriptsDir, { recursive: true, mode: 0o700 });
221
+ await chmod(this.transcriptsDir, 0o700);
222
+ await this.ensureFile(this.directoriesLogPath);
223
+ await this.ensureFile(this.workspacesLogPath);
224
+ await this.ensureFile(this.sessionsLogPath);
225
+ await this.ensureFile(this.turnsLogPath);
226
+ await this.ensureFile(this.queuesLogPath);
227
+ await this.loadSnapshot();
228
+ await this.replayLogs();
229
+ await this.repairTranscriptLogs();
230
+ this.rebuildSessionMetadataFromTranscripts();
231
+
232
+ if (await this.shouldCompact()) {
233
+ await this.compact();
234
+ }
235
+ } catch (error) {
236
+ await this.releaseDataDirLock();
237
+ throw error;
238
+ }
239
+ }
240
+
241
+ async releaseDataDirLock() {
242
+ const lock = this.dataDirLock;
243
+ this.dataDirLock = null;
244
+ await lock?.release();
245
+ }
246
+
247
+ private async ensureFile(filePath: string) {
248
+ const handle = await open(filePath, 'a', 0o600);
249
+ await handle.close();
250
+ await chmod(filePath, 0o600);
251
+ }
252
+
253
+ private async loadSnapshot() {
254
+ const snapshotText = await readTextIfExists(this.snapshotPath);
255
+ if (snapshotText?.trim()) {
256
+ try {
257
+ this.applySnapshot(JSON.parse(snapshotText) as SnapshotFile);
258
+ return;
259
+ } catch (error) {
260
+ const quarantinePath = await quarantineFile(this.snapshotPath);
261
+ console.warn(
262
+ `${LOG_PREFIX} Failed to load snapshot; quarantined it at ${quarantinePath}:`,
263
+ error,
264
+ );
265
+ this.resetState();
266
+ }
267
+ }
268
+
269
+ const backupText = await readTextIfExists(this.snapshotBackupPath);
270
+ if (!backupText?.trim()) return;
271
+
272
+ try {
273
+ this.applySnapshot(JSON.parse(backupText) as SnapshotFile);
274
+ await atomicWriteFile(this.snapshotPath, backupText);
275
+ console.warn(`${LOG_PREFIX} Recovered local history from the previous snapshot.`);
276
+ } catch (error) {
277
+ const quarantinePath = await quarantineFile(this.snapshotBackupPath);
278
+ console.warn(
279
+ `${LOG_PREFIX} Failed to load previous snapshot; quarantined it at ${quarantinePath}:`,
280
+ error,
281
+ );
282
+ this.resetState();
283
+ }
284
+ }
285
+
286
+ private applySnapshot(parsed: SnapshotFile) {
287
+ if (
288
+ !Array.isArray(parsed.directories) ||
289
+ !Array.isArray(parsed.workspaces) ||
290
+ !Array.isArray(parsed.sessions) ||
291
+ (parsed.queuedMessages !== undefined && !Array.isArray(parsed.queuedMessages))
292
+ ) {
293
+ throw new Error('Snapshot has an invalid shape');
294
+ }
295
+
296
+ for (const directory of parsed.directories) {
297
+ this.state.directoriesById.set(directory.id, { ...directory });
298
+ }
299
+ for (const workspace of parsed.workspaces) {
300
+ this.state.workspacesById.set(workspace.id, { ...workspace });
301
+ }
302
+ for (const session of parsed.sessions) {
303
+ this.state.sessionsById.set(session.id, { ...session });
304
+ }
305
+ for (const queued of parsed.queuedMessages ?? []) {
306
+ const normalized = this.normalizeQueuedMessage(queued);
307
+ this.state.queuedMessagesById.set(normalized.id, normalized);
308
+ }
309
+ }
310
+
311
+ private resetState() {
312
+ this.state.directoriesById.clear();
313
+ this.state.workspacesById.clear();
314
+ this.state.sessionsById.clear();
315
+ this.state.queuedMessagesById.clear();
316
+ this.cachedTranscript = null;
317
+ this.nextQueueSequence = 1;
318
+ }
319
+
320
+ private async replayLogs() {
321
+ await this.replayLog<DirectoryEvent>(this.directoriesLogPath);
322
+ await this.replayLog<WorkspaceEvent>(this.workspacesLogPath);
323
+ await this.replayLog<SessionEvent>(this.sessionsLogPath);
324
+ await this.replayLog<TurnEvent>(this.turnsLogPath);
325
+ await this.replayLog<QueueEvent>(this.queuesLogPath);
326
+ }
327
+
328
+ private async replayLog<_TEvent extends StoreEvent>(filePath: string) {
329
+ const text = await readTextIfExists(filePath);
330
+ if (text === null) return;
331
+ if (!text.trim()) return;
332
+
333
+ const lines = text.split('\n');
334
+ const validLines: string[] = [];
335
+ let corruptionDetected = false;
336
+
337
+ for (const rawLine of lines) {
338
+ const line = rawLine.trim();
339
+ if (!line) continue;
340
+ try {
341
+ const event = JSON.parse(line) as Partial<StoreEvent>;
342
+ this.applyEvent(event as StoreEvent);
343
+ validLines.push(line);
344
+ } catch {
345
+ corruptionDetected = true;
346
+ }
347
+ }
348
+
349
+ if (!corruptionDetected) return;
350
+
351
+ const quarantinePath = await quarantineFile(filePath);
352
+ await atomicWriteFile(filePath, validLines.length > 0 ? `${validLines.join('\n')}\n` : '');
353
+ console.warn(
354
+ `${LOG_PREFIX} Recovered valid events from ${path.basename(filePath)}; quarantined the damaged log at ${quarantinePath}.`,
355
+ );
356
+ }
357
+
358
+ private async repairTranscriptLogs() {
359
+ const entries = await readdir(this.transcriptsDir, { withFileTypes: true });
360
+ for (const entry of entries) {
361
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
362
+
363
+ const filePath = path.join(this.transcriptsDir, entry.name);
364
+ const text = await readTextIfExists(filePath);
365
+ if (!text?.trim()) continue;
366
+
367
+ const validLines: string[] = [];
368
+ let corruptionDetected = false;
369
+ for (const rawLine of text.split('\n')) {
370
+ const line = rawLine.trim();
371
+ if (!line) continue;
372
+ try {
373
+ JSON.parse(line);
374
+ validLines.push(line);
375
+ } catch {
376
+ corruptionDetected = true;
377
+ }
378
+ }
379
+
380
+ if (!corruptionDetected) {
381
+ await chmod(filePath, 0o600);
382
+ continue;
383
+ }
384
+
385
+ const quarantinePath = await quarantineFile(filePath);
386
+ await atomicWriteFile(filePath, validLines.length > 0 ? `${validLines.join('\n')}\n` : '');
387
+ console.warn(
388
+ `${LOG_PREFIX} Recovered valid transcript entries from ${entry.name}; quarantined the damaged transcript at ${quarantinePath}.`,
389
+ );
390
+ }
391
+ }
392
+
393
+ private applyEvent(event: StoreEvent) {
394
+ switch (event.type) {
395
+ case 'directory_added': {
396
+ const localPath = resolveLocalPath(event.localPath);
397
+ this.state.directoriesById.set(event.directoryId, {
398
+ id: event.directoryId,
399
+ localPath,
400
+ title: event.title,
401
+ githubOwner: event.githubOwner,
402
+ githubRepo: event.githubRepo,
403
+ defaultBranchName: event.defaultBranchName,
404
+ createdAt: event.timestamp,
405
+ updatedAt: event.timestamp,
406
+ });
407
+ break;
408
+ }
409
+ case 'directory_removed': {
410
+ const directory = this.state.directoriesById.get(event.directoryId);
411
+ if (!directory) break;
412
+
413
+ directory.removedAt = event.timestamp;
414
+ directory.updatedAt = event.timestamp;
415
+ break;
416
+ }
417
+ case 'workspace_created': {
418
+ this.state.workspacesById.set(event.workspaceId, {
419
+ id: event.workspaceId,
420
+ directoryId: event.directoryId,
421
+ localPath: resolveLocalPath(event.localPath),
422
+ branchName: event.branchName,
423
+ setupState: 'creating',
424
+ reviewState: 'in_progress',
425
+ visibilityState: 'active',
426
+ hasUnreadAgentResult: false,
427
+ createdAt: event.timestamp,
428
+ updatedAt: event.timestamp,
429
+ });
430
+ break;
431
+ }
432
+ case 'workspace_removed': {
433
+ const workspace = this.state.workspacesById.get(event.workspaceId);
434
+ if (!workspace) break;
435
+
436
+ workspace.removedAt = event.timestamp;
437
+ workspace.updatedAt = event.timestamp;
438
+ break;
439
+ }
440
+ case 'workspace_setup_completed': {
441
+ const workspace = this.state.workspacesById.get(event.workspaceId);
442
+ if (!workspace) break;
443
+ workspace.setupState = 'ready';
444
+ workspace.setupError = undefined;
445
+ workspace.updatedAt = event.timestamp;
446
+ break;
447
+ }
448
+ case 'workspace_setup_failed': {
449
+ const workspace = this.state.workspacesById.get(event.workspaceId);
450
+ if (!workspace) break;
451
+ workspace.setupState = 'failed';
452
+ workspace.setupError = event.error;
453
+ workspace.updatedAt = event.timestamp;
454
+ break;
455
+ }
456
+ case 'workspace_branch_name_changed': {
457
+ const workspace = this.state.workspacesById.get(event.workspaceId);
458
+ if (!workspace) break;
459
+
460
+ workspace.branchName = event.branchName;
461
+ workspace.updatedAt = event.timestamp;
462
+ break;
463
+ }
464
+ case 'workspace_review_state_changed': {
465
+ const workspace = this.state.workspacesById.get(event.workspaceId);
466
+ if (!workspace) break;
467
+ workspace.reviewState = event.reviewState;
468
+ workspace.updatedAt = event.timestamp;
469
+ break;
470
+ }
471
+ case 'workspace_visibility_changed': {
472
+ const workspace = this.state.workspacesById.get(event.workspaceId);
473
+ if (!workspace) break;
474
+ workspace.visibilityState = event.visibilityState;
475
+ workspace.updatedAt = event.timestamp;
476
+ break;
477
+ }
478
+ case 'workspace_pr_observed': {
479
+ const workspace = this.state.workspacesById.get(event.workspaceId);
480
+ if (!workspace) break;
481
+ workspace.pullRequest = event.pullRequest;
482
+ workspace.updatedAt = event.timestamp;
483
+ break;
484
+ }
485
+ case 'workspace_pr_cleared': {
486
+ const workspace = this.state.workspacesById.get(event.workspaceId);
487
+ if (!workspace) break;
488
+ workspace.pullRequest = undefined;
489
+ workspace.updatedAt = event.timestamp;
490
+ break;
491
+ }
492
+ case 'workspace_unread_agent_result_set': {
493
+ const workspace = this.state.workspacesById.get(event.workspaceId);
494
+ if (!workspace) break;
495
+ workspace.hasUnreadAgentResult = event.hasUnreadAgentResult;
496
+ workspace.updatedAt = event.timestamp;
497
+ break;
498
+ }
499
+ case 'session_created': {
500
+ this.state.sessionsById.set(event.sessionId, {
501
+ id: event.sessionId,
502
+ workspaceId: event.workspaceId,
503
+ title: event.title,
504
+ createdAt: event.timestamp,
505
+ updatedAt: event.timestamp,
506
+ provider: null,
507
+ planMode: false,
508
+ sessionToken: null,
509
+ lastTurnOutcome: null,
510
+ });
511
+ break;
512
+ }
513
+ case 'session_renamed': {
514
+ const session = this.state.sessionsById.get(event.sessionId);
515
+ if (!session) break;
516
+
517
+ session.title = event.title;
518
+ session.updatedAt = event.timestamp;
519
+ break;
520
+ }
521
+ case 'session_removed': {
522
+ const session = this.state.sessionsById.get(event.sessionId);
523
+ if (!session) break;
524
+ session.removedAt = event.timestamp;
525
+ session.updatedAt = event.timestamp;
526
+ for (const queued of this.state.queuedMessagesById.values()) {
527
+ if (queued.sessionId === event.sessionId) {
528
+ this.state.queuedMessagesById.delete(queued.id);
529
+ }
530
+ }
531
+ break;
532
+ }
533
+ case 'session_provider_set': {
534
+ const session = this.state.sessionsById.get(event.sessionId);
535
+ if (!session) break;
536
+ session.provider = event.provider;
537
+ session.updatedAt = event.timestamp;
538
+ break;
539
+ }
540
+ case 'session_plan_mode_set': {
541
+ const session = this.state.sessionsById.get(event.sessionId);
542
+ if (!session) break;
543
+ session.planMode = event.planMode;
544
+ session.updatedAt = event.timestamp;
545
+ break;
546
+ }
547
+ case 'turn_started': {
548
+ const session = this.state.sessionsById.get(event.sessionId);
549
+ if (!session) break;
550
+ session.updatedAt = event.timestamp;
551
+ break;
552
+ }
553
+ case 'turn_finished': {
554
+ const session = this.state.sessionsById.get(event.sessionId);
555
+ if (!session) break;
556
+ session.updatedAt = event.timestamp;
557
+ session.lastTurnOutcome = 'success';
558
+ break;
559
+ }
560
+ case 'turn_failed': {
561
+ const session = this.state.sessionsById.get(event.sessionId);
562
+ if (!session) break;
563
+ session.updatedAt = event.timestamp;
564
+ session.lastTurnOutcome = 'failed';
565
+ break;
566
+ }
567
+ case 'turn_cancelled': {
568
+ const session = this.state.sessionsById.get(event.sessionId);
569
+ if (!session) break;
570
+ session.updatedAt = event.timestamp;
571
+ session.lastTurnOutcome = 'cancelled';
572
+ break;
573
+ }
574
+ case 'session_token_set': {
575
+ const session = this.state.sessionsById.get(event.sessionId);
576
+ if (!session) break;
577
+ session.sessionToken = event.sessionToken;
578
+ session.updatedAt = event.timestamp;
579
+ break;
580
+ }
581
+ case 'session_message_queued': {
582
+ const message = this.normalizeQueuedMessage(event.message);
583
+ if (!this.getSession(message.sessionId)) break;
584
+ this.state.queuedMessagesById.set(message.id, message);
585
+ break;
586
+ }
587
+ case 'session_message_claimed': {
588
+ const queued = this.state.queuedMessagesById.get(event.messageId);
589
+ if (!queued || queued.sessionId !== event.sessionId || queued.status !== 'queued') break;
590
+ queued.status = 'draining';
591
+ queued.promptEntryId = event.promptEntryId;
592
+ queued.updatedAt = event.timestamp;
593
+ break;
594
+ }
595
+ case 'session_message_requeued': {
596
+ const queued = this.state.queuedMessagesById.get(event.messageId);
597
+ if (!queued || queued.sessionId !== event.sessionId || queued.status !== 'draining') break;
598
+ queued.status = 'queued';
599
+ queued.updatedAt = event.timestamp;
600
+ break;
601
+ }
602
+ case 'session_message_completed':
603
+ case 'session_message_failed':
604
+ case 'session_message_dequeued': {
605
+ const queued = this.state.queuedMessagesById.get(event.messageId);
606
+ if (!queued || queued.sessionId !== event.sessionId) break;
607
+ this.state.queuedMessagesById.delete(event.messageId);
608
+ break;
609
+ }
610
+ case 'session_queue_cleared': {
611
+ for (const queued of this.state.queuedMessagesById.values()) {
612
+ if (queued.sessionId === event.sessionId) {
613
+ this.state.queuedMessagesById.delete(queued.id);
614
+ }
615
+ }
616
+ break;
617
+ }
618
+ }
619
+ }
620
+
621
+ private applyMessageMetadata(sessionId: string, entry: TranscriptEntry) {
622
+ const session = this.state.sessionsById.get(sessionId);
623
+ if (!session) return;
624
+
625
+ if (!entry.hidden && (entry.kind === 'user_prompt' || entry.kind === 'assistant_text')) {
626
+ session.lastMessageAt = entry.createdAt;
627
+ }
628
+
629
+ if (entry.kind === 'assistant_text' && !entry.hidden) {
630
+ session.lastAssistantPreview = messagePreview(entry.text);
631
+ }
632
+
633
+ session.updatedAt = Math.max(session.updatedAt, entry.createdAt);
634
+ }
635
+
636
+ private append<TEvent extends StoreEvent>(filePath: string, event: TEvent) {
637
+ return this.enqueueWrite(async () => {
638
+ await this.appendEventNow(filePath, event);
639
+ });
640
+ }
641
+
642
+ private async appendEventNow<TEvent extends StoreEvent>(filePath: string, event: TEvent) {
643
+ await appendFile(filePath, `${JSON.stringify(event)}\n`, {
644
+ encoding: 'utf8',
645
+ mode: 0o600,
646
+ });
647
+ this.applyEvent(event);
648
+ }
649
+
650
+ private enqueueWrite<T>(operation: () => Promise<T>) {
651
+ const run = this.writeChain.catch(() => undefined).then(operation);
652
+ this.writeChain = run.then(
653
+ () => undefined,
654
+ () => undefined,
655
+ );
656
+ return run;
657
+ }
658
+
659
+ private transcriptPath(sessionId: string) {
660
+ return path.join(this.transcriptsDir, `${sessionId}.jsonl`);
661
+ }
662
+
663
+ private workspaceUploadDir(workspaceId: string) {
664
+ return path.join(this.dataDir, 'uploads', workspaceId);
665
+ }
666
+
667
+ private scratchpadPath(workspaceId: string) {
668
+ return path.join(this.dataDir, 'scratchpads', `${workspaceId}.md`);
669
+ }
670
+
671
+ private workspaceInstructionAttachmentPaths(workspaceId: string) {
672
+ const instructionsDir = path.join(this.dataDir, 'agent-instructions');
673
+ return [
674
+ path.join(instructionsDir, `create-pr-${workspaceId}.md`),
675
+ path.join(instructionsDir, `failing-ci-${workspaceId}.txt`),
676
+ path.join(instructionsDir, `merge-conflict-${workspaceId}.md`),
677
+ path.join(instructionsDir, `selected-review-comments-${workspaceId}.txt`),
678
+ ];
679
+ }
680
+
681
+ private async deleteSessionData(sessionId: string) {
682
+ if (this.cachedTranscript?.sessionId === sessionId) {
683
+ this.cachedTranscript = null;
684
+ }
685
+ await rm(this.transcriptPath(sessionId), { force: true });
686
+ }
687
+
688
+ private async deleteWorkspaceOwnedData(workspaceId: string) {
689
+ await Promise.all([
690
+ rm(this.workspaceUploadDir(workspaceId), { recursive: true, force: true }),
691
+ rm(this.scratchpadPath(workspaceId), { force: true }),
692
+ ...this.workspaceInstructionAttachmentPaths(workspaceId).map((filePath) =>
693
+ rm(filePath, { force: true }),
694
+ ),
695
+ ]);
696
+ }
697
+
698
+ private loadTranscriptFromDisk(sessionId: string) {
699
+ const transcriptPath = this.transcriptPath(sessionId);
700
+ if (!existsSync(transcriptPath)) return [];
701
+
702
+ const text = readFileSyncImmediate(transcriptPath, 'utf-8');
703
+ if (!text.trim()) return [];
704
+
705
+ const entries: TranscriptEntry[] = [];
706
+ const lines = text.split('\n');
707
+ let lastNonEmpty = -1;
708
+ for (let index = lines.length - 1; index >= 0; index--) {
709
+ if (lines[index].trim()) {
710
+ lastNonEmpty = index;
711
+ break;
712
+ }
713
+ }
714
+
715
+ for (let index = 0; index < lines.length; index++) {
716
+ const rawLine = lines[index];
717
+ const line = rawLine.trim();
718
+ if (!line) continue;
719
+ try {
720
+ entries.push(JSON.parse(line) as TranscriptEntry);
721
+ } catch (error) {
722
+ if (index === lastNonEmpty) {
723
+ console.warn(`${LOG_PREFIX} Ignoring corrupt trailing line in transcript ${sessionId}`);
724
+ return entries;
725
+ }
726
+
727
+ console.warn(`${LOG_PREFIX} Stopped reading corrupt transcript ${sessionId}:`, error);
728
+ return entries;
729
+ }
730
+ }
731
+ return entries;
732
+ }
733
+
734
+ private rebuildSessionMetadataFromTranscripts() {
735
+ for (const session of this.state.sessionsById.values()) {
736
+ if (session.removedAt) continue;
737
+ for (const entry of this.loadTranscriptFromDisk(session.id)) {
738
+ this.applyMessageMetadata(session.id, entry);
739
+ }
740
+ }
741
+ }
742
+
743
+ async addDirectory(args: {
744
+ localPath: string;
745
+ title?: string;
746
+ githubOwner: string;
747
+ githubRepo: string;
748
+ }) {
749
+ const localPath = resolveLocalPath(args.localPath);
750
+ const existing = this.listDirectories().find((directory) => directory.localPath === localPath);
751
+ if (existing) return existing;
752
+
753
+ const directoryId = crypto.randomUUID();
754
+ const event: DirectoryEvent = {
755
+ type: 'directory_added',
756
+ timestamp: Date.now(),
757
+ directoryId,
758
+ localPath,
759
+ title: args.title?.trim() || path.basename(localPath) || localPath,
760
+ githubOwner: args.githubOwner,
761
+ githubRepo: args.githubRepo,
762
+ defaultBranchName: 'main',
763
+ };
764
+
765
+ await this.append(this.directoriesLogPath, event);
766
+ return this.requireDirectory(directoryId);
767
+ }
768
+
769
+ async removeDirectory(directoryId: string) {
770
+ this.requireDirectory(directoryId);
771
+ const workspaces = this.listWorkspacesByDirectory(directoryId);
772
+ for (const workspace of workspaces) {
773
+ await this.removeWorkspace(workspace.id);
774
+ }
775
+ const event: DirectoryEvent = {
776
+ type: 'directory_removed',
777
+ timestamp: Date.now(),
778
+ directoryId,
779
+ };
780
+
781
+ await this.append(this.directoriesLogPath, event);
782
+ }
783
+
784
+ async createWorkspace(args: { directoryId: string; localPath: string; branchName: string }) {
785
+ this.requireDirectory(args.directoryId);
786
+ const localPath = resolveLocalPath(args.localPath);
787
+ const existingWorkspaces = this.listWorkspaces();
788
+ if (existingWorkspaces.some((workspace) => workspace.localPath === localPath)) {
789
+ throw new Error('Workspace path is already in use');
790
+ }
791
+
792
+ if (
793
+ existingWorkspaces.some(
794
+ (workspace) =>
795
+ workspace.directoryId === args.directoryId && workspace.branchName === args.branchName,
796
+ )
797
+ ) {
798
+ throw new Error('Workspace branch is already in use for this directory');
799
+ }
800
+
801
+ const workspaceId = crypto.randomUUID();
802
+ const event: WorkspaceEvent = {
803
+ type: 'workspace_created',
804
+ timestamp: Date.now(),
805
+ workspaceId,
806
+ directoryId: args.directoryId,
807
+ localPath,
808
+ branchName: args.branchName,
809
+ };
810
+
811
+ await this.append(this.workspacesLogPath, event);
812
+ return this.requireWorkspace(workspaceId);
813
+ }
814
+
815
+ async removeWorkspace(workspaceId: string) {
816
+ this.requireWorkspace(workspaceId);
817
+ const sessions = this.listSessionsByWorkspace(workspaceId);
818
+ for (const session of sessions) {
819
+ await this.removeSession(session.id);
820
+ }
821
+ const event: WorkspaceEvent = {
822
+ type: 'workspace_removed',
823
+ timestamp: Date.now(),
824
+ workspaceId,
825
+ };
826
+
827
+ await this.append(this.workspacesLogPath, event);
828
+ await this.deleteWorkspaceOwnedData(workspaceId);
829
+ }
830
+
831
+ async markWorkspaceSetupCompleted(workspaceId: string) {
832
+ const workspace = this.requireWorkspace(workspaceId);
833
+ if (workspace.setupState === 'ready') return;
834
+
835
+ const event: WorkspaceEvent = {
836
+ type: 'workspace_setup_completed',
837
+ timestamp: Date.now(),
838
+ workspaceId,
839
+ };
840
+
841
+ await this.append(this.workspacesLogPath, event);
842
+ }
843
+
844
+ async markWorkspaceSetupFailed(workspaceId: string, error: string) {
845
+ const workspace = this.requireWorkspace(workspaceId);
846
+ if (workspace.setupState === 'failed' && workspace.setupError === error) return;
847
+
848
+ const event: WorkspaceEvent = {
849
+ type: 'workspace_setup_failed',
850
+ timestamp: Date.now(),
851
+ workspaceId,
852
+ error,
853
+ };
854
+
855
+ await this.append(this.workspacesLogPath, event);
856
+ }
857
+
858
+ async setWorkspaceBranch(workspaceId: string, branchName: string) {
859
+ const workspace = this.requireWorkspace(workspaceId);
860
+ if (workspace.branchName === branchName) return;
861
+
862
+ const event: WorkspaceEvent = {
863
+ type: 'workspace_branch_name_changed',
864
+ timestamp: Date.now(),
865
+ workspaceId,
866
+ branchName,
867
+ };
868
+
869
+ await this.append(this.workspacesLogPath, event);
870
+ }
871
+
872
+ async setWorkspaceReviewState(workspaceId: string, reviewState: WorkspaceReviewState) {
873
+ const workspace = this.requireWorkspace(workspaceId);
874
+ if (workspace.reviewState === reviewState) return;
875
+
876
+ const event: WorkspaceEvent = {
877
+ type: 'workspace_review_state_changed',
878
+ timestamp: Date.now(),
879
+ workspaceId,
880
+ reviewState,
881
+ };
882
+
883
+ await this.append(this.workspacesLogPath, event);
884
+ }
885
+
886
+ async setWorkspaceVisibilityState(
887
+ workspaceId: string,
888
+ visibilityState: WorkspaceVisibilityState,
889
+ ) {
890
+ const workspace = this.requireWorkspace(workspaceId);
891
+ if (workspace.visibilityState === visibilityState) return;
892
+
893
+ const event: WorkspaceEvent = {
894
+ type: 'workspace_visibility_changed',
895
+ timestamp: Date.now(),
896
+ workspaceId,
897
+ visibilityState,
898
+ };
899
+
900
+ await this.append(this.workspacesLogPath, event);
901
+ }
902
+
903
+ async observeWorkspacePullRequest(workspaceId: string, pullRequest: WorkspacePullRequestSummary) {
904
+ const workspace = this.requireWorkspace(workspaceId);
905
+ if (
906
+ workspace.pullRequest?.number === pullRequest.number &&
907
+ workspace.pullRequest.status === pullRequest.status &&
908
+ workspace.pullRequest.title === pullRequest.title &&
909
+ workspace.pullRequest.body === pullRequest.body &&
910
+ workspace.pullRequest.url === pullRequest.url &&
911
+ workspace.pullRequest.headRefName === pullRequest.headRefName &&
912
+ workspace.pullRequest.baseRefName === pullRequest.baseRefName &&
913
+ workspace.pullRequest.ciStatus === pullRequest.ciStatus &&
914
+ workspace.pullRequest.isDraft === pullRequest.isDraft &&
915
+ workspace.pullRequest.mergeStateStatus === pullRequest.mergeStateStatus &&
916
+ workspace.pullRequest.hasMergeConflicts === pullRequest.hasMergeConflicts &&
917
+ workspace.pullRequest.unresolvedCommentCount === pullRequest.unresolvedCommentCount &&
918
+ workspace.pullRequest.additions === pullRequest.additions &&
919
+ workspace.pullRequest.deletions === pullRequest.deletions &&
920
+ workspace.pullRequest.createdAt === pullRequest.createdAt &&
921
+ pullRequestFilesEqual(workspace.pullRequest.files, pullRequest.files) &&
922
+ pullRequestCommentsEqual(workspace.pullRequest.comments, pullRequest.comments) &&
923
+ pullRequestChecksEqual(workspace.pullRequest.checks, pullRequest.checks)
924
+ ) {
925
+ return;
926
+ }
927
+
928
+ const event: WorkspaceEvent = {
929
+ type: 'workspace_pr_observed',
930
+ timestamp: Date.now(),
931
+ workspaceId,
932
+ pullRequest,
933
+ };
934
+
935
+ await this.append(this.workspacesLogPath, event);
936
+ }
937
+
938
+ async clearWorkspacePullRequest(workspaceId: string) {
939
+ const workspace = this.requireWorkspace(workspaceId);
940
+ if (!workspace.pullRequest) return;
941
+
942
+ const event: WorkspaceEvent = {
943
+ type: 'workspace_pr_cleared',
944
+ timestamp: Date.now(),
945
+ workspaceId,
946
+ };
947
+
948
+ await this.append(this.workspacesLogPath, event);
949
+ }
950
+
951
+ async setWorkspaceUnreadAgentResult(workspaceId: string, hasUnreadAgentResult: boolean) {
952
+ const workspace = this.requireWorkspace(workspaceId);
953
+ if (workspace.hasUnreadAgentResult === hasUnreadAgentResult) return;
954
+
955
+ const event: WorkspaceEvent = {
956
+ type: 'workspace_unread_agent_result_set',
957
+ timestamp: Date.now(),
958
+ workspaceId,
959
+ hasUnreadAgentResult,
960
+ };
961
+
962
+ await this.append(this.workspacesLogPath, event);
963
+ }
964
+
965
+ async createSession(workspaceId: string, title = 'Untitled') {
966
+ const workspace = this.requireWorkspace(workspaceId);
967
+ if (workspace.setupState !== 'ready') {
968
+ throw new Error('Workspace is not ready');
969
+ }
970
+ const sessionId = crypto.randomUUID();
971
+ const event: SessionEvent = {
972
+ type: 'session_created',
973
+ timestamp: Date.now(),
974
+ sessionId,
975
+ workspaceId,
976
+ title,
977
+ };
978
+
979
+ await this.append(this.sessionsLogPath, event);
980
+ return this.requireSession(sessionId);
981
+ }
982
+
983
+ async renameSession(sessionId: string, title: string) {
984
+ const trimmed = title.trim();
985
+ if (!trimmed) return;
986
+
987
+ const session = this.requireSession(sessionId);
988
+
989
+ if (session.title === trimmed) return;
990
+ const event: SessionEvent = {
991
+ type: 'session_renamed',
992
+ timestamp: Date.now(),
993
+ sessionId,
994
+ title: trimmed,
995
+ };
996
+
997
+ await this.append(this.sessionsLogPath, event);
998
+ }
999
+
1000
+ async removeSession(sessionId: string) {
1001
+ this.requireSession(sessionId);
1002
+ const event: SessionEvent = {
1003
+ type: 'session_removed',
1004
+ timestamp: Date.now(),
1005
+ sessionId,
1006
+ };
1007
+
1008
+ await this.append(this.sessionsLogPath, event);
1009
+ await this.deleteSessionData(sessionId);
1010
+ }
1011
+
1012
+ async enqueueSessionMessage(sessionId: string, payload: QueuedSessionSendPayload) {
1013
+ return this.enqueueWrite(async () => {
1014
+ this.requireSession(sessionId);
1015
+ const queueSize = [...this.state.queuedMessagesById.values()].filter(
1016
+ (message) => message.sessionId === sessionId,
1017
+ ).length;
1018
+ if (queueSize >= MAX_QUEUED_SESSION_MESSAGES) {
1019
+ throw new Error(`Too many queued messages (max ${MAX_QUEUED_SESSION_MESSAGES}).`);
1020
+ }
1021
+
1022
+ const id = crypto.randomUUID();
1023
+ const timestamp = Date.now();
1024
+ const message: QueuedSessionMessageRecord = {
1025
+ id,
1026
+ sessionId,
1027
+ payload: structuredClone(payload),
1028
+ status: 'queued',
1029
+ sequence: this.nextQueueSequence,
1030
+ promptEntryId: `queued-prompt:${id}`,
1031
+ createdAt: timestamp,
1032
+ updatedAt: timestamp,
1033
+ };
1034
+ this.nextQueueSequence += 1;
1035
+
1036
+ await this.appendEventNow(this.queuesLogPath, {
1037
+ type: 'session_message_queued',
1038
+ timestamp,
1039
+ message,
1040
+ } satisfies QueueEvent);
1041
+ return structuredClone(message);
1042
+ });
1043
+ }
1044
+
1045
+ async claimNextQueuedSessionMessage(sessionId: string) {
1046
+ return this.enqueueWrite(async () => {
1047
+ if (!this.getSession(sessionId)) return null;
1048
+ const messages = [...this.state.queuedMessagesById.values()].filter(
1049
+ (message) => message.sessionId === sessionId,
1050
+ );
1051
+ if (messages.some((message) => message.status === 'draining')) return null;
1052
+
1053
+ const next =
1054
+ messages
1055
+ .filter((message) => message.status === 'queued')
1056
+ .toSorted((left, right) => left.sequence - right.sequence)[0] ?? null;
1057
+ if (!next) return null;
1058
+ await this.appendEventNow(this.queuesLogPath, {
1059
+ type: 'session_message_claimed',
1060
+ timestamp: Date.now(),
1061
+ sessionId,
1062
+ messageId: next.id,
1063
+ promptEntryId: next.promptEntryId,
1064
+ } satisfies QueueEvent);
1065
+ return this.getQueuedSessionMessage(next.id);
1066
+ });
1067
+ }
1068
+
1069
+ async requeueSessionMessage(sessionId: string, messageId: string) {
1070
+ return this.enqueueWrite(async () => {
1071
+ const queued = this.state.queuedMessagesById.get(messageId);
1072
+ if (!queued || queued.sessionId !== sessionId || queued.status !== 'draining') return null;
1073
+ await this.appendEventNow(this.queuesLogPath, {
1074
+ type: 'session_message_requeued',
1075
+ timestamp: Date.now(),
1076
+ sessionId,
1077
+ messageId,
1078
+ } satisfies QueueEvent);
1079
+ return this.getQueuedSessionMessage(messageId);
1080
+ });
1081
+ }
1082
+
1083
+ private async finishQueuedSessionMessage(
1084
+ type: 'session_message_completed' | 'session_message_failed' | 'session_message_dequeued',
1085
+ sessionId: string,
1086
+ messageId: string,
1087
+ ) {
1088
+ return this.enqueueWrite(async () => {
1089
+ const queued = this.state.queuedMessagesById.get(messageId);
1090
+ if (!queued || queued.sessionId !== sessionId) return null;
1091
+ const snapshot = structuredClone(queued);
1092
+ await this.appendEventNow(this.queuesLogPath, {
1093
+ type,
1094
+ timestamp: Date.now(),
1095
+ sessionId,
1096
+ messageId,
1097
+ } satisfies QueueEvent);
1098
+ return snapshot;
1099
+ });
1100
+ }
1101
+
1102
+ completeQueuedSessionMessage(sessionId: string, messageId: string) {
1103
+ return this.finishQueuedSessionMessage('session_message_completed', sessionId, messageId);
1104
+ }
1105
+
1106
+ failQueuedSessionMessage(sessionId: string, messageId: string) {
1107
+ return this.finishQueuedSessionMessage('session_message_failed', sessionId, messageId);
1108
+ }
1109
+
1110
+ dequeueSessionMessage(sessionId: string, messageId: string) {
1111
+ return this.finishQueuedSessionMessage('session_message_dequeued', sessionId, messageId);
1112
+ }
1113
+
1114
+ async clearQueuedSessionMessages(sessionId: string) {
1115
+ return this.enqueueWrite(async () => {
1116
+ const queued = [...this.state.queuedMessagesById.values()]
1117
+ .filter((message) => message.sessionId === sessionId)
1118
+ .map((message) => structuredClone(message));
1119
+ if (queued.length === 0) return [];
1120
+ await this.appendEventNow(this.queuesLogPath, {
1121
+ type: 'session_queue_cleared',
1122
+ timestamp: Date.now(),
1123
+ sessionId,
1124
+ } satisfies QueueEvent);
1125
+ return queued;
1126
+ });
1127
+ }
1128
+
1129
+ async setSessionProvider(sessionId: string, provider: AgentProvider) {
1130
+ const session = this.requireSession(sessionId);
1131
+ if (session.provider === provider) return;
1132
+
1133
+ const event: SessionEvent = {
1134
+ type: 'session_provider_set',
1135
+ timestamp: Date.now(),
1136
+ sessionId,
1137
+ provider,
1138
+ };
1139
+
1140
+ await this.append(this.sessionsLogPath, event);
1141
+ }
1142
+
1143
+ async setPlanMode(sessionId: string, planMode: boolean) {
1144
+ const session = this.requireSession(sessionId);
1145
+ if (session.planMode === planMode) return;
1146
+
1147
+ const event: SessionEvent = {
1148
+ type: 'session_plan_mode_set',
1149
+ timestamp: Date.now(),
1150
+ sessionId,
1151
+ planMode,
1152
+ };
1153
+
1154
+ await this.append(this.sessionsLogPath, event);
1155
+ }
1156
+
1157
+ async appendMessage(sessionId: string, entry: TranscriptEntry) {
1158
+ this.requireSession(sessionId);
1159
+ const storedEntry = cloneTranscriptEntries([entry])[0];
1160
+
1161
+ return this.enqueueWrite(async () => {
1162
+ await this.appendMessageNow(sessionId, storedEntry);
1163
+ });
1164
+ }
1165
+
1166
+ async appendMessageOnce(sessionId: string, entry: TranscriptEntry) {
1167
+ this.requireSession(sessionId);
1168
+ const storedEntry = cloneTranscriptEntries([entry])[0];
1169
+ return this.enqueueWrite(async () => {
1170
+ const entries =
1171
+ this.cachedTranscript?.sessionId === sessionId
1172
+ ? this.cachedTranscript.entries
1173
+ : this.loadTranscriptFromDisk(sessionId);
1174
+ if (entries.some((candidate) => candidate._id === storedEntry._id)) return false;
1175
+ await this.appendMessageNow(sessionId, storedEntry);
1176
+ return true;
1177
+ });
1178
+ }
1179
+
1180
+ private async appendMessageNow(sessionId: string, storedEntry: TranscriptEntry) {
1181
+ await mkdir(this.transcriptsDir, { recursive: true, mode: 0o700 });
1182
+ await appendFile(this.transcriptPath(sessionId), `${JSON.stringify(storedEntry)}\n`, {
1183
+ encoding: 'utf8',
1184
+ mode: 0o600,
1185
+ });
1186
+ this.applyMessageMetadata(sessionId, storedEntry);
1187
+ if (this.cachedTranscript?.sessionId === sessionId) {
1188
+ this.cachedTranscript.entries.push(storedEntry);
1189
+ }
1190
+ }
1191
+
1192
+ async recordTurnStarted(sessionId: string) {
1193
+ this.requireSession(sessionId);
1194
+ await this.append(this.turnsLogPath, {
1195
+ type: 'turn_started',
1196
+ timestamp: Date.now(),
1197
+ sessionId,
1198
+ } satisfies TurnEvent);
1199
+ }
1200
+
1201
+ async recordTurnFinished(sessionId: string) {
1202
+ const session = this.requireSession(sessionId);
1203
+ await this.append(this.turnsLogPath, {
1204
+ type: 'turn_finished',
1205
+ timestamp: Date.now(),
1206
+ sessionId,
1207
+ } satisfies TurnEvent);
1208
+ await this.setWorkspaceUnreadAgentResult(session.workspaceId, true);
1209
+ }
1210
+
1211
+ async recordTurnFailed(sessionId: string, error: string) {
1212
+ const session = this.requireSession(sessionId);
1213
+ await this.append(this.turnsLogPath, {
1214
+ type: 'turn_failed',
1215
+ timestamp: Date.now(),
1216
+ sessionId,
1217
+ error,
1218
+ } satisfies TurnEvent);
1219
+
1220
+ await this.setWorkspaceUnreadAgentResult(session.workspaceId, true);
1221
+ }
1222
+
1223
+ async recordTurnCancelled(sessionId: string) {
1224
+ this.requireSession(sessionId);
1225
+ await this.append(this.turnsLogPath, {
1226
+ type: 'turn_cancelled',
1227
+ timestamp: Date.now(),
1228
+ sessionId,
1229
+ } satisfies TurnEvent);
1230
+ }
1231
+
1232
+ async setSessionToken(sessionId: string, sessionToken: string | null) {
1233
+ const session = this.requireSession(sessionId);
1234
+ if (session.sessionToken === sessionToken) return;
1235
+
1236
+ await this.append(this.turnsLogPath, {
1237
+ type: 'session_token_set',
1238
+ timestamp: Date.now(),
1239
+ sessionId,
1240
+ sessionToken,
1241
+ } satisfies TurnEvent);
1242
+ }
1243
+
1244
+ getDirectory(directoryId: string) {
1245
+ const directory = this.state.directoriesById.get(directoryId);
1246
+ if (!directory || directory.removedAt) return null;
1247
+ return directory;
1248
+ }
1249
+
1250
+ requireDirectory(directoryId: string) {
1251
+ const directory = this.getDirectory(directoryId);
1252
+ if (!directory) throw new Error('Directory not found');
1253
+ return directory;
1254
+ }
1255
+
1256
+ getWorkspace(workspaceId: string) {
1257
+ const workspace = this.state.workspacesById.get(workspaceId);
1258
+ if (!workspace || workspace.removedAt) return null;
1259
+ if (!this.getDirectory(workspace.directoryId)) return null;
1260
+ return workspace;
1261
+ }
1262
+
1263
+ requireWorkspace(workspaceId: string) {
1264
+ const workspace = this.getWorkspace(workspaceId);
1265
+ if (!workspace) throw new Error('Workspace not found');
1266
+ return workspace;
1267
+ }
1268
+
1269
+ getSession(sessionId: string) {
1270
+ const session = this.state.sessionsById.get(sessionId);
1271
+ if (!session || session.removedAt) return null;
1272
+ return session;
1273
+ }
1274
+
1275
+ requireSession(sessionId: string) {
1276
+ const session = this.getSession(sessionId);
1277
+ if (!session) throw new Error('Session not found');
1278
+ return session;
1279
+ }
1280
+
1281
+ getQueuedSessionMessage(messageId: string) {
1282
+ const queued = this.state.queuedMessagesById.get(messageId);
1283
+ if (!queued || !this.getSession(queued.sessionId)) return null;
1284
+ return structuredClone(queued);
1285
+ }
1286
+
1287
+ listQueuedSessionMessages(sessionId: string) {
1288
+ this.requireSession(sessionId);
1289
+ return [...this.state.queuedMessagesById.values()]
1290
+ .filter((message) => message.sessionId === sessionId && message.status === 'queued')
1291
+ .toSorted((left, right) => left.sequence - right.sequence)
1292
+ .map((message) => structuredClone(message));
1293
+ }
1294
+
1295
+ listDrainingSessionMessages() {
1296
+ return [...this.state.queuedMessagesById.values()]
1297
+ .filter(
1298
+ (message) => message.status === 'draining' && Boolean(this.getSession(message.sessionId)),
1299
+ )
1300
+ .toSorted((left, right) => left.sequence - right.sequence)
1301
+ .map((message) => structuredClone(message));
1302
+ }
1303
+
1304
+ listSessionIdsWithQueuedMessages() {
1305
+ return [
1306
+ ...new Set(
1307
+ [...this.state.queuedMessagesById.values()]
1308
+ .filter((message) => Boolean(this.getSession(message.sessionId)))
1309
+ .map((message) => message.sessionId),
1310
+ ),
1311
+ ];
1312
+ }
1313
+
1314
+ hasQueuedSessionMessages(sessionId: string) {
1315
+ if (!this.getSession(sessionId)) return false;
1316
+ return [...this.state.queuedMessagesById.values()].some(
1317
+ (message) => message.sessionId === sessionId,
1318
+ );
1319
+ }
1320
+
1321
+ getMessages(sessionId: string) {
1322
+ if (this.cachedTranscript?.sessionId === sessionId) {
1323
+ return cloneTranscriptEntries(this.cachedTranscript.entries);
1324
+ }
1325
+
1326
+ const entries = this.loadTranscriptFromDisk(sessionId);
1327
+ this.cachedTranscript = { sessionId, entries };
1328
+ return cloneTranscriptEntries(entries);
1329
+ }
1330
+
1331
+ private getMessagesPageFromEntries(
1332
+ entries: TranscriptEntry[],
1333
+ limit: number,
1334
+ beforeIndex?: number,
1335
+ ): TranscriptPageResult {
1336
+ if (entries.length === 0) return { entries: [], hasOlder: false, olderCursor: null };
1337
+ const endIndex =
1338
+ beforeIndex === undefined
1339
+ ? entries.length
1340
+ : Math.max(0, Math.min(beforeIndex, entries.length));
1341
+ const startIndex = Math.max(0, endIndex - limit);
1342
+ return {
1343
+ entries: cloneTranscriptEntries(entries.slice(startIndex, endIndex)),
1344
+ hasOlder: startIndex > 0,
1345
+ olderCursor: startIndex > 0 ? encodeHistoryCursor(startIndex) : null,
1346
+ };
1347
+ }
1348
+
1349
+ getRecentMessagesPage(sessionId: string, limit: number): SessionHistoryPage {
1350
+ if (limit <= 0) return { messages: [], hasOlder: false, olderCursor: null };
1351
+ const { entries, ...rest } = this.getMessagesPageFromEntries(
1352
+ this.getMessages(sessionId),
1353
+ limit,
1354
+ );
1355
+ return { messages: entries, ...rest };
1356
+ }
1357
+
1358
+ getMessagesPageBefore(
1359
+ sessionId: string,
1360
+ beforeCursor: string,
1361
+ limit: number,
1362
+ ): SessionHistoryPage {
1363
+ if (limit <= 0) return { messages: [], hasOlder: false, olderCursor: null };
1364
+ const beforeIndex = decodeCursor(beforeCursor);
1365
+ const { entries, ...rest } = this.getMessagesPageFromEntries(
1366
+ this.getMessages(sessionId),
1367
+ limit,
1368
+ beforeIndex,
1369
+ );
1370
+ return { messages: entries, ...rest };
1371
+ }
1372
+
1373
+ getRecentSessionHistory(sessionId: string, recentLimit: number) {
1374
+ const page = this.getRecentMessagesPage(sessionId, recentLimit);
1375
+ return {
1376
+ messages: page.messages,
1377
+ history: getHistorySnapshot(
1378
+ {
1379
+ entries: page.messages,
1380
+ hasOlder: page.hasOlder,
1381
+ olderCursor: page.olderCursor,
1382
+ },
1383
+ recentLimit,
1384
+ ),
1385
+ };
1386
+ }
1387
+
1388
+ listDirectories() {
1389
+ return [...this.state.directoriesById.values()].filter((directory) => !directory.removedAt);
1390
+ }
1391
+
1392
+ listWorkspaces() {
1393
+ return [...this.state.workspacesById.values()].filter(
1394
+ (workspace) => !workspace.removedAt && Boolean(this.getDirectory(workspace.directoryId)),
1395
+ );
1396
+ }
1397
+
1398
+ listWorkspacesByDirectory(directoryId: string) {
1399
+ return this.listWorkspaces().filter((workspace) => workspace.directoryId === directoryId);
1400
+ }
1401
+
1402
+ listSessionsByWorkspace(workspaceId: string) {
1403
+ return [...this.state.sessionsById.values()].filter(
1404
+ (session) => session.workspaceId === workspaceId && !session.removedAt,
1405
+ );
1406
+ }
1407
+
1408
+ private createSnapshot(): SnapshotFile {
1409
+ return {
1410
+ generatedAt: Date.now(),
1411
+ directories: [...this.state.directoriesById.values()].map((directory) => ({
1412
+ ...directory,
1413
+ })),
1414
+ workspaces: [...this.state.workspacesById.values()].map((workspace) => ({ ...workspace })),
1415
+ sessions: [...this.state.sessionsById.values()].map((session) => ({ ...session })),
1416
+ queuedMessages: [...this.state.queuedMessagesById.values()].map((message) =>
1417
+ structuredClone(message),
1418
+ ),
1419
+ };
1420
+ }
1421
+
1422
+ async compact() {
1423
+ return this.enqueueWrite(async () => {
1424
+ const snapshot = this.createSnapshot();
1425
+ await atomicWriteFile(this.snapshotPath, JSON.stringify(snapshot, null, 2), {
1426
+ backupPath: this.snapshotBackupPath,
1427
+ });
1428
+ await Promise.all([
1429
+ atomicWriteFile(this.directoriesLogPath, ''),
1430
+ atomicWriteFile(this.workspacesLogPath, ''),
1431
+ atomicWriteFile(this.sessionsLogPath, ''),
1432
+ atomicWriteFile(this.turnsLogPath, ''),
1433
+ atomicWriteFile(this.queuesLogPath, ''),
1434
+ ]);
1435
+ });
1436
+ }
1437
+
1438
+ private async shouldCompact() {
1439
+ const sizes = await Promise.all([
1440
+ Bun.file(this.directoriesLogPath).size,
1441
+ Bun.file(this.workspacesLogPath).size,
1442
+ Bun.file(this.sessionsLogPath).size,
1443
+ Bun.file(this.turnsLogPath).size,
1444
+ Bun.file(this.queuesLogPath).size,
1445
+ ]);
1446
+ return sizes.reduce((total, size) => total + size, 0) >= COMPACTION_THRESHOLD_BYTES;
1447
+ }
1448
+ }