kanna-code 0.20.0 → 0.21.1
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.
- package/dist/client/assets/abap-BdImnpbu.js +1 -0
- package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/client/assets/ada-bCR0ucgS.js +1 -0
- package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/client/assets/apache-Pmp26Uib.js +1 -0
- package/dist/client/assets/apex-D8_7TLub.js +1 -0
- package/dist/client/assets/apl-dKokRX4l.js +1 -0
- package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/client/assets/ara-BRHolxvo.js +1 -0
- package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/client/assets/astro-CbQHKStN.js +1 -0
- package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/client/assets/awk-DMzUqQB5.js +1 -0
- package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/client/assets/bat-BkioyH1T.js +1 -0
- package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/client/assets/berry-uYugtg8r.js +1 -0
- package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/client/assets/bird2-DPOp833l.js +1 -0
- package/dist/client/assets/blade-D4QpJJKB.js +1 -0
- package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/client/assets/c-BIGW1oBm.js +1 -0
- package/dist/client/assets/c3-eo99z4R2.js +1 -0
- package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/client/assets/clarity-D53aC0YG.js +1 -0
- package/dist/client/assets/clojure-P80f7IUj.js +1 -0
- package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/client/assets/cobol-nwyudZeR.js +1 -0
- package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/client/assets/coq-DkFqJrB1.js +1 -0
- package/dist/client/assets/cpp-CofmeUqb.js +1 -0
- package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/client/assets/css-DPfMkruS.js +1 -0
- package/dist/client/assets/csv-fuZLfV_i.js +1 -0
- package/dist/client/assets/cue-D82EKSYY.js +1 -0
- package/dist/client/assets/cypher-COkxafJQ.js +1 -0
- package/dist/client/assets/d-85-TOEBH.js +1 -0
- package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/client/assets/dart-CF10PKvl.js +1 -0
- package/dist/client/assets/dax-CEL-wOlO.js +1 -0
- package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/client/assets/diff-D97Zzqfu.js +1 -0
- package/dist/client/assets/docker-BcOcwvcX.js +1 -0
- package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/client/assets/edge-BkV0erSs.js +1 -0
- package/dist/client/assets/elixir-CDX3lj18.js +1 -0
- package/dist/client/assets/elm-DbKCFpqz.js +1 -0
- package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/client/assets/erb-B12qg9BL.js +1 -0
- package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/client/assets/fennel-BYunw83y.js +1 -0
- package/dist/client/assets/fish-BvzEVeQv.js +1 -0
- package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/client/assets/genie-D0YGMca9.js +1 -0
- package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/client/assets/gleam-BspZqrRM.js +1 -0
- package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/client/assets/glsl-DplSGwfg.js +1 -0
- package/dist/client/assets/gn-n2N0HUVH.js +1 -0
- package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/client/assets/go-CxLEBnE3.js +1 -0
- package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/client/assets/hack-CaT9iCJl.js +1 -0
- package/dist/client/assets/haml-B8DHNrY2.js +1 -0
- package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/client/assets/hjson-D5-asLiD.js +1 -0
- package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
- package/dist/client/assets/houston-DnULxvSX.js +1 -0
- package/dist/client/assets/html-GMplVEZG.js +1 -0
- package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/client/assets/http-jrhK8wxY.js +1 -0
- package/dist/client/assets/hurl-irOxFIW8.js +1 -0
- package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/client/assets/hy-DFXneXwc.js +1 -0
- package/dist/client/assets/imba-DGztddWO.js +1 -0
- package/dist/client/assets/index-BsILdq_R.js +2502 -0
- package/dist/client/assets/index-C_DLHgnt.css +32 -0
- package/dist/client/assets/ini-BEwlwnbL.js +1 -0
- package/dist/client/assets/java-CylS5w8V.js +1 -0
- package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/client/assets/jison-wvAkD_A8.js +1 -0
- package/dist/client/assets/json-Cp-IABpG.js +1 -0
- package/dist/client/assets/json5-C9tS-k6U.js +1 -0
- package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/client/assets/julia-CxzCAyBv.js +1 -0
- package/dist/client/assets/just-Cw27pwNe.js +1 -0
- package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/client/assets/kdl-DV7GczEv.js +1 -0
- package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/client/assets/kusto-DZf3V79B.js +1 -0
- package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/client/assets/less-B1dDrJ26.js +1 -0
- package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/client/assets/liquid-DYVedYrR.js +1 -0
- package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/client/assets/log-2UxHyX5q.js +1 -0
- package/dist/client/assets/logo-BtOb2qkB.js +1 -0
- package/dist/client/assets/lua-BaeVxFsk.js +1 -0
- package/dist/client/assets/luau-C-HG3fhB.js +1 -0
- package/dist/client/assets/make-CHLpvVh8.js +1 -0
- package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/client/assets/marko-CnJfTvn9.js +1 -0
- package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/client/assets/matlab-D7o27uSR.js +1 -0
- package/dist/client/assets/mdc-BMNejdWA.js +1 -0
- package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/client/assets/min-light-CTRr51gU.js +1 -0
- package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/client/assets/move-IF9eRakj.js +1 -0
- package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
- package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
- package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/client/assets/nim-CVrawwO9.js +1 -0
- package/dist/client/assets/nix-CwoSXNpI.js +1 -0
- package/dist/client/assets/nord-Ddv68eIx.js +1 -0
- package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/client/assets/odin-BBf5iR-q.js +1 -0
- package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/client/assets/perl-C0TMdlhV.js +1 -0
- package/dist/client/assets/php-Dhbhpdrm.js +1 -0
- package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
- package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
- package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/client/assets/po-BTJTHyun.js +1 -0
- package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/client/assets/polar-C0HS_06l.js +1 -0
- package/dist/client/assets/postcss-CXtECtnM.js +1 -0
- package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/client/assets/pug-CGlum2m_.js +1 -0
- package/dist/client/assets/puppet-BMWR74SV.js +1 -0
- package/dist/client/assets/purescript-CklMAg4u.js +1 -0
- package/dist/client/assets/python-B6aJPvgy.js +1 -0
- package/dist/client/assets/qml-3beO22l8.js +1 -0
- package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/client/assets/qss-IeuSbFQv.js +1 -0
- package/dist/client/assets/r-Dspwwk_N.js +1 -0
- package/dist/client/assets/racket-BqYA7rlc.js +1 -0
- package/dist/client/assets/raku-DXvB9xmW.js +1 -0
- package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
- package/dist/client/assets/red-bN70gL4F.js +1 -0
- package/dist/client/assets/reg-C-SQnVFl.js +1 -0
- package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/client/assets/rel-C3B-1QV4.js +1 -0
- package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/client/assets/ron-D8l8udqQ.js +1 -0
- package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/client/assets/rst-BrH8l1NY.js +1 -0
- package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
- package/dist/client/assets/rust-B1yitclQ.js +1 -0
- package/dist/client/assets/sas-cz2c8ADy.js +1 -0
- package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/client/assets/scala-C151Ov-r.js +1 -0
- package/dist/client/assets/scheme-C98Dy4si.js +1 -0
- package/dist/client/assets/scss-OYdSNvt2.js +1 -0
- package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/client/assets/solidity-rGO070M0.js +1 -0
- package/dist/client/assets/soy-Brmx7dQM.js +1 -0
- package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/client/assets/sql-BLtJtn59.js +1 -0
- package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/client/assets/stata-BH5u7GGu.js +1 -0
- package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
- package/dist/client/assets/swift-D82vCrfD.js +1 -0
- package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/client/assets/templ-P3uqSqPl.js +1 -0
- package/dist/client/assets/terraform-BETggiCN.js +1 -0
- package/dist/client/assets/tex-idrVyKtj.js +1 -0
- package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/client/assets/toml-vGWfd6FD.js +1 -0
- package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/client/assets/tsx-COt5Ahok.js +1 -0
- package/dist/client/assets/turtle-BsS91CYL.js +1 -0
- package/dist/client/assets/twig-DNn4PbVi.js +1 -0
- package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/client/assets/typst-DHCkPAjA.js +1 -0
- package/dist/client/assets/v-BcVCzyr7.js +1 -0
- package/dist/client/assets/vala-CsfeWuGM.js +1 -0
- package/dist/client/assets/vb-D17OF-Vu.js +1 -0
- package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/client/assets/vesper-DU1UobuO.js +1 -0
- package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/client/assets/viml-CJc9bBzg.js +1 -0
- package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/client/assets/vue-DN_0RTcg.js +1 -0
- package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
- package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/client/assets/wit-5i3qLPDT.js +1 -0
- package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/client/assets/yaml-Buea-lGh.js +1 -0
- package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/client/assets/zig-VOosw3JB.js +1 -0
- package/dist/client/index.html +2 -2
- package/package.json +2 -1
- package/src/server/agent.test.ts +45 -1
- package/src/server/agent.ts +141 -8
- package/src/server/codex-app-server-protocol.ts +28 -0
- package/src/server/codex-app-server.test.ts +88 -0
- package/src/server/codex-app-server.ts +71 -1
- package/src/server/diff-store.test.ts +126 -0
- package/src/server/diff-store.ts +365 -0
- package/src/server/generate-commit-message.test.ts +59 -0
- package/src/server/generate-commit-message.ts +121 -0
- package/src/server/read-models.test.ts +42 -1
- package/src/server/read-models.ts +10 -3
- package/src/server/server.ts +60 -1
- package/src/server/terminal-manager.test.ts +37 -4
- package/src/server/uploads.ts +4 -0
- package/src/server/ws-router.ts +122 -7
- package/src/shared/protocol.ts +3 -0
- package/src/shared/types.ts +50 -0
- package/dist/client/assets/index-Cdjl9mRd.js +0 -606
- package/dist/client/assets/index-D6aACRxb.css +0 -32
|
@@ -54,7 +54,7 @@ describe("read models", () => {
|
|
|
54
54
|
lastTurnOutcome: null,
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
const chat = deriveChatSnapshot(state, new Map(), new Set(), "chat-1", () => [])
|
|
57
|
+
const chat = deriveChatSnapshot(state, new Map(), new Set(), "chat-1", () => [], () => ({ status: "unknown", files: [] }))
|
|
58
58
|
expect(chat?.runtime.provider).toBe("claude")
|
|
59
59
|
expect(chat?.availableProviders.length).toBeGreaterThan(1)
|
|
60
60
|
expect(chat?.availableProviders.find((provider) => provider.id === "codex")?.models.map((model) => model.id)).toEqual([
|
|
@@ -106,4 +106,45 @@ describe("read models", () => {
|
|
|
106
106
|
},
|
|
107
107
|
])
|
|
108
108
|
})
|
|
109
|
+
|
|
110
|
+
test("orders sidebar chats by user-visible activity instead of internal updatedAt churn", () => {
|
|
111
|
+
const state = createEmptyState()
|
|
112
|
+
state.projectsById.set("project-1", {
|
|
113
|
+
id: "project-1",
|
|
114
|
+
localPath: "/tmp/project",
|
|
115
|
+
title: "Project",
|
|
116
|
+
createdAt: 1,
|
|
117
|
+
updatedAt: 1,
|
|
118
|
+
})
|
|
119
|
+
state.projectIdsByPath.set("/tmp/project", "project-1")
|
|
120
|
+
state.chatsById.set("chat-old", {
|
|
121
|
+
id: "chat-old",
|
|
122
|
+
projectId: "project-1",
|
|
123
|
+
title: "Older user activity",
|
|
124
|
+
createdAt: 10,
|
|
125
|
+
updatedAt: 500,
|
|
126
|
+
unread: false,
|
|
127
|
+
provider: "claude",
|
|
128
|
+
planMode: false,
|
|
129
|
+
sessionToken: null,
|
|
130
|
+
lastMessageAt: 100,
|
|
131
|
+
lastTurnOutcome: null,
|
|
132
|
+
})
|
|
133
|
+
state.chatsById.set("chat-new", {
|
|
134
|
+
id: "chat-new",
|
|
135
|
+
projectId: "project-1",
|
|
136
|
+
title: "Newer user activity",
|
|
137
|
+
createdAt: 20,
|
|
138
|
+
updatedAt: 50,
|
|
139
|
+
unread: false,
|
|
140
|
+
provider: "claude",
|
|
141
|
+
planMode: false,
|
|
142
|
+
sessionToken: null,
|
|
143
|
+
lastMessageAt: 200,
|
|
144
|
+
lastTurnOutcome: null,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const sidebar = deriveSidebarData(state, new Map())
|
|
148
|
+
expect(sidebar.projectGroups[0]?.chats.map((chat) => chat.chatId)).toEqual(["chat-new", "chat-old"])
|
|
149
|
+
})
|
|
109
150
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ChatDiffSnapshot,
|
|
2
3
|
ChatRuntime,
|
|
3
4
|
ChatSnapshot,
|
|
4
5
|
KannaStatus,
|
|
@@ -17,6 +18,10 @@ export function deriveStatus(chat: ChatRecord, activeStatus?: KannaStatus): Kann
|
|
|
17
18
|
return "idle"
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function getSidebarChatSortTimestamp(chat: ChatRecord) {
|
|
22
|
+
return chat.lastMessageAt ?? chat.createdAt
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export function deriveSidebarData(
|
|
21
26
|
state: StoreState,
|
|
22
27
|
activeStatuses: Map<string, KannaStatus>
|
|
@@ -28,7 +33,7 @@ export function deriveSidebarData(
|
|
|
28
33
|
const projectGroups: SidebarProjectGroup[] = projects.map((project) => {
|
|
29
34
|
const chats: SidebarChatRow[] = [...state.chatsById.values()]
|
|
30
35
|
.filter((chat) => chat.projectId === project.id && !chat.deletedAt)
|
|
31
|
-
.sort((a, b) => (b
|
|
36
|
+
.sort((a, b) => getSidebarChatSortTimestamp(b) - getSidebarChatSortTimestamp(a))
|
|
32
37
|
.map((chat) => ({
|
|
33
38
|
_id: chat.id,
|
|
34
39
|
_creationTime: chat.createdAt,
|
|
@@ -73,7 +78,7 @@ export function deriveLocalProjectsSnapshot(
|
|
|
73
78
|
for (const project of [...state.projectsById.values()].filter((entry) => !entry.deletedAt)) {
|
|
74
79
|
const chats = [...state.chatsById.values()].filter((chat) => chat.projectId === project.id && !chat.deletedAt)
|
|
75
80
|
const lastOpenedAt = chats.reduce(
|
|
76
|
-
(latest, chat) => Math.max(latest, chat
|
|
81
|
+
(latest, chat) => Math.max(latest, getSidebarChatSortTimestamp(chat)),
|
|
77
82
|
project.updatedAt
|
|
78
83
|
)
|
|
79
84
|
|
|
@@ -100,7 +105,8 @@ export function deriveChatSnapshot(
|
|
|
100
105
|
activeStatuses: Map<string, KannaStatus>,
|
|
101
106
|
drainingChatIds: Set<string>,
|
|
102
107
|
chatId: string,
|
|
103
|
-
getMessages: (chatId: string) => ChatSnapshot["messages"]
|
|
108
|
+
getMessages: (chatId: string) => ChatSnapshot["messages"],
|
|
109
|
+
getDiffs: (chatId: string) => ChatDiffSnapshot
|
|
104
110
|
): ChatSnapshot | null {
|
|
105
111
|
const chat = state.chatsById.get(chatId)
|
|
106
112
|
if (!chat || chat.deletedAt) return null
|
|
@@ -122,6 +128,7 @@ export function deriveChatSnapshot(
|
|
|
122
128
|
return {
|
|
123
129
|
runtime,
|
|
124
130
|
messages: getMessages(chat.id),
|
|
131
|
+
diffs: getDiffs(chat.id),
|
|
125
132
|
availableProviders: [...SERVER_PROVIDERS],
|
|
126
133
|
}
|
|
127
134
|
}
|
package/src/server/server.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { APP_NAME, getRuntimeProfile } from "../shared/branding"
|
|
|
4
4
|
import type { ChatAttachment } from "../shared/types"
|
|
5
5
|
import { EventStore } from "./event-store"
|
|
6
6
|
import { AgentCoordinator } from "./agent"
|
|
7
|
+
import { DiffStore } from "./diff-store"
|
|
7
8
|
import { discoverProjects, type DiscoveredProject } from "./discovery"
|
|
8
9
|
import { KeybindingsManager } from "./keybindings"
|
|
9
10
|
import { getMachineDisplayName } from "./machine-name"
|
|
@@ -11,7 +12,7 @@ import { TerminalManager } from "./terminal-manager"
|
|
|
11
12
|
import { UpdateManager } from "./update-manager"
|
|
12
13
|
import type { UpdateInstallAttemptResult } from "./cli-runtime"
|
|
13
14
|
import { createWsRouter, type ClientState } from "./ws-router"
|
|
14
|
-
import { deleteProjectUpload, inferAttachmentContentType, persistProjectUpload } from "./uploads"
|
|
15
|
+
import { deleteProjectUpload, inferAttachmentContentType, inferProjectFileContentType, persistProjectUpload } from "./uploads"
|
|
15
16
|
import { getProjectUploadDir } from "./paths"
|
|
16
17
|
|
|
17
18
|
const MAX_UPLOAD_FILES = 10
|
|
@@ -68,8 +69,10 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
68
69
|
const hostname = options.host ?? "127.0.0.1"
|
|
69
70
|
const strictPort = options.strictPort ?? false
|
|
70
71
|
const store = new EventStore()
|
|
72
|
+
const diffStore = new DiffStore(store.dataDir)
|
|
71
73
|
const machineDisplayName = getMachineDisplayName()
|
|
72
74
|
await store.initialize()
|
|
75
|
+
await diffStore.initialize()
|
|
73
76
|
await store.migrateLegacyTranscripts(options.onMigrationProgress)
|
|
74
77
|
let discoveredProjects: DiscoveredProject[] = []
|
|
75
78
|
|
|
@@ -101,6 +104,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
101
104
|
})
|
|
102
105
|
router = createWsRouter({
|
|
103
106
|
store,
|
|
107
|
+
diffStore,
|
|
104
108
|
agent,
|
|
105
109
|
terminals,
|
|
106
110
|
keybindings,
|
|
@@ -127,6 +131,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
127
131
|
const upgraded = serverInstance.upgrade(req, {
|
|
128
132
|
data: {
|
|
129
133
|
subscriptions: new Map(),
|
|
134
|
+
snapshotSignatures: new Map(),
|
|
130
135
|
},
|
|
131
136
|
})
|
|
132
137
|
return upgraded ? undefined : new Response("WebSocket upgrade failed", { status: 400 })
|
|
@@ -151,6 +156,11 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
151
156
|
return attachmentContentResponse
|
|
152
157
|
}
|
|
153
158
|
|
|
159
|
+
const projectFileContentResponse = await handleProjectFileContent(req, url, store)
|
|
160
|
+
if (projectFileContentResponse) {
|
|
161
|
+
return projectFileContentResponse
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
return serveStatic(distDir, url.pathname)
|
|
155
165
|
},
|
|
156
166
|
websocket: {
|
|
@@ -191,6 +201,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
|
|
|
191
201
|
return {
|
|
192
202
|
port: actualPort,
|
|
193
203
|
store,
|
|
204
|
+
diffStore,
|
|
194
205
|
updateManager,
|
|
195
206
|
stop: shutdown,
|
|
196
207
|
}
|
|
@@ -289,6 +300,54 @@ async function handleAttachmentContent(req: Request, url: URL, store: EventStore
|
|
|
289
300
|
})
|
|
290
301
|
}
|
|
291
302
|
|
|
303
|
+
async function handleProjectFileContent(req: Request, url: URL, store: EventStore) {
|
|
304
|
+
const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/files\/([^/]+)\/content$/)
|
|
305
|
+
if (!match) {
|
|
306
|
+
return null
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (req.method !== "GET") {
|
|
310
|
+
return new Response(null, {
|
|
311
|
+
status: 405,
|
|
312
|
+
headers: {
|
|
313
|
+
Allow: "GET",
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const project = store.getProject(match[1])
|
|
319
|
+
if (!project) {
|
|
320
|
+
return Response.json({ error: "Project not found" }, { status: 404 })
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const relativePath = path.posix.normalize(decodeURIComponent(match[2]).replaceAll("\\", "/"))
|
|
324
|
+
if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("/../") || path.posix.isAbsolute(relativePath)) {
|
|
325
|
+
return Response.json({ error: "Invalid project file path" }, { status: 400 })
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const filePath = path.resolve(project.localPath, relativePath)
|
|
329
|
+
const projectRoot = path.resolve(project.localPath)
|
|
330
|
+
if (filePath !== projectRoot && !filePath.startsWith(`${projectRoot}${path.sep}`)) {
|
|
331
|
+
return Response.json({ error: "Invalid project file path" }, { status: 400 })
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const file = Bun.file(filePath)
|
|
335
|
+
try {
|
|
336
|
+
const info = await stat(filePath)
|
|
337
|
+
if (!info.isFile()) {
|
|
338
|
+
return Response.json({ error: "File not found" }, { status: 404 })
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
return Response.json({ error: "File not found" }, { status: 404 })
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return new Response(file, {
|
|
345
|
+
headers: {
|
|
346
|
+
"Content-Type": inferProjectFileContentType(relativePath, file.type),
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
292
351
|
async function handleProjectUploadDelete(req: Request, url: URL, store: EventStore) {
|
|
293
352
|
if (req.method !== "DELETE") {
|
|
294
353
|
return null
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { afterEach, beforeAll, describe, expect, test } from "bun:test"
|
|
2
|
-
import { mkdtemp, rm } from "node:fs/promises"
|
|
1
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtemp, mkdir, rm } from "node:fs/promises"
|
|
3
3
|
import os from "node:os"
|
|
4
4
|
import path from "node:path"
|
|
5
5
|
import { TerminalManager } from "./terminal-manager"
|
|
@@ -13,10 +13,19 @@ const isSupportedPlatform = process.platform !== "win32" && typeof Bun.Terminal
|
|
|
13
13
|
const describeIfSupported = isSupportedPlatform ? describe : describe.skip
|
|
14
14
|
|
|
15
15
|
let tempProjectPath = ""
|
|
16
|
+
let tempHomePath = ""
|
|
17
|
+
const originalHome = process.env.HOME
|
|
18
|
+
const originalZdotdir = process.env.ZDOTDIR
|
|
19
|
+
const originalHistfile = process.env.HISTFILE
|
|
16
20
|
|
|
17
21
|
beforeAll(async () => {
|
|
18
22
|
if (!isSupportedPlatform) return
|
|
19
23
|
tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-manager-"))
|
|
24
|
+
tempHomePath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-home-"))
|
|
25
|
+
await mkdir(path.join(tempHomePath, ".config"), { recursive: true })
|
|
26
|
+
process.env.HOME = tempHomePath
|
|
27
|
+
process.env.ZDOTDIR = tempHomePath
|
|
28
|
+
process.env.HISTFILE = path.join(tempHomePath, ".zsh_history")
|
|
20
29
|
})
|
|
21
30
|
|
|
22
31
|
afterEach(async () => {
|
|
@@ -25,6 +34,30 @@ afterEach(async () => {
|
|
|
25
34
|
tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-manager-"))
|
|
26
35
|
})
|
|
27
36
|
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
if (originalHome === undefined) {
|
|
39
|
+
delete process.env.HOME
|
|
40
|
+
} else {
|
|
41
|
+
process.env.HOME = originalHome
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (originalZdotdir === undefined) {
|
|
45
|
+
delete process.env.ZDOTDIR
|
|
46
|
+
} else {
|
|
47
|
+
process.env.ZDOTDIR = originalZdotdir
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (originalHistfile === undefined) {
|
|
51
|
+
delete process.env.HISTFILE
|
|
52
|
+
} else {
|
|
53
|
+
process.env.HISTFILE = originalHistfile
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (tempHomePath) {
|
|
57
|
+
await rm(tempHomePath, { recursive: true, force: true })
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
28
61
|
async function waitFor(check: () => boolean, timeoutMs: number, intervalMs = 25) {
|
|
29
62
|
const startedAt = Date.now()
|
|
30
63
|
while (Date.now() - startedAt < timeoutMs) {
|
|
@@ -70,8 +103,8 @@ describeIfSupported("TerminalManager", () => {
|
|
|
70
103
|
const { manager, getOutput } = await createSession(terminalId)
|
|
71
104
|
|
|
72
105
|
try {
|
|
73
|
-
manager.write(terminalId,
|
|
74
|
-
await waitFor(() => getOutput().includes("
|
|
106
|
+
manager.write(terminalId, `python3 -c "import time; print('__KANNA_SLEEP__', flush=True); time.sleep(30)"\r`)
|
|
107
|
+
await waitFor(() => getOutput().includes("__KANNA_SLEEP__"), COMMAND_TIMEOUT_MS)
|
|
75
108
|
|
|
76
109
|
manager.write(terminalId, "\x03")
|
|
77
110
|
manager.write(terminalId, "printf '__KANNA_AFTER_INT__\\n'\r")
|
package/src/server/uploads.ts
CHANGED
|
@@ -108,6 +108,10 @@ export function inferAttachmentContentType(fileName: string, fallbackType?: stri
|
|
|
108
108
|
return fallbackType || DEFAULT_BINARY_MIME_TYPE
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
export function inferProjectFileContentType(fileName: string, fallbackType?: string): string {
|
|
112
|
+
return inferAttachmentContentType(fileName, fallbackType)
|
|
113
|
+
}
|
|
114
|
+
|
|
111
115
|
export async function deleteProjectUpload(args: {
|
|
112
116
|
localPath: string
|
|
113
117
|
storedName: string
|
package/src/server/ws-router.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ClientEnvelope, ServerEnvelope, SubscriptionTopic } from "../share
|
|
|
4
4
|
import { isClientEnvelope } from "../shared/protocol"
|
|
5
5
|
import type { AgentCoordinator } from "./agent"
|
|
6
6
|
import type { DiscoveredProject } from "./discovery"
|
|
7
|
+
import { DiffStore } from "./diff-store"
|
|
7
8
|
import { EventStore } from "./event-store"
|
|
8
9
|
import { openExternal } from "./external-open"
|
|
9
10
|
import { KeybindingsManager } from "./keybindings"
|
|
@@ -14,10 +15,12 @@ import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } fr
|
|
|
14
15
|
|
|
15
16
|
export interface ClientState {
|
|
16
17
|
subscriptions: Map<string, SubscriptionTopic>
|
|
18
|
+
snapshotSignatures: Map<string, string>
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface CreateWsRouterArgs {
|
|
20
22
|
store: EventStore
|
|
23
|
+
diffStore?: Pick<DiffStore, "getSnapshot" | "refreshSnapshot" | "generateCommitMessage" | "commitFiles">
|
|
21
24
|
agent: AgentCoordinator
|
|
22
25
|
terminals: TerminalManager
|
|
23
26
|
keybindings: KeybindingsManager
|
|
@@ -31,8 +34,17 @@ function send(ws: ServerWebSocket<ClientState>, message: ServerEnvelope) {
|
|
|
31
34
|
ws.send(JSON.stringify(message))
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
function ensureSnapshotSignatures(ws: ServerWebSocket<ClientState>) {
|
|
38
|
+
if (!ws.data.snapshotSignatures) {
|
|
39
|
+
ws.data.snapshotSignatures = new Map()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return ws.data.snapshotSignatures
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
export function createWsRouter({
|
|
35
46
|
store,
|
|
47
|
+
diffStore,
|
|
36
48
|
agent,
|
|
37
49
|
terminals,
|
|
38
50
|
keybindings,
|
|
@@ -42,6 +54,12 @@ export function createWsRouter({
|
|
|
42
54
|
updateManager,
|
|
43
55
|
}: CreateWsRouterArgs) {
|
|
44
56
|
const sockets = new Set<ServerWebSocket<ClientState>>()
|
|
57
|
+
const resolvedDiffStore = diffStore ?? {
|
|
58
|
+
getSnapshot: () => ({ status: "unknown", branchName: undefined, files: [] as const }),
|
|
59
|
+
refreshSnapshot: async () => false,
|
|
60
|
+
generateCommitMessage: async () => ({ subject: "Update selected files", body: "", usedFallback: true, failureMessage: null }),
|
|
61
|
+
commitFiles: async () => false,
|
|
62
|
+
}
|
|
45
63
|
|
|
46
64
|
function createEnvelope(id: string, topic: SubscriptionTopic): ServerEnvelope {
|
|
47
65
|
if (topic.type === "sidebar") {
|
|
@@ -121,14 +139,29 @@ export function createWsRouter({
|
|
|
121
139
|
id,
|
|
122
140
|
snapshot: {
|
|
123
141
|
type: "chat",
|
|
124
|
-
data: deriveChatSnapshot(
|
|
142
|
+
data: deriveChatSnapshot(
|
|
143
|
+
store.state,
|
|
144
|
+
agent.getActiveStatuses(),
|
|
145
|
+
agent.getDrainingChatIds(),
|
|
146
|
+
topic.chatId,
|
|
147
|
+
(chatId) => store.getMessages(chatId),
|
|
148
|
+
(chatId) => resolvedDiffStore.getSnapshot(chatId)
|
|
149
|
+
),
|
|
125
150
|
},
|
|
126
151
|
}
|
|
127
152
|
}
|
|
128
153
|
|
|
129
154
|
function pushSnapshots(ws: ServerWebSocket<ClientState>) {
|
|
155
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
130
156
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
131
|
-
|
|
157
|
+
const envelope = createEnvelope(id, topic)
|
|
158
|
+
if (envelope.type !== "snapshot") continue
|
|
159
|
+
const signature = JSON.stringify(envelope.snapshot)
|
|
160
|
+
if (snapshotSignatures.get(id) === signature) {
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
snapshotSignatures.set(id, signature)
|
|
164
|
+
send(ws, envelope)
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
167
|
|
|
@@ -150,9 +183,15 @@ export function createWsRouter({
|
|
|
150
183
|
|
|
151
184
|
function pushTerminalSnapshot(terminalId: string) {
|
|
152
185
|
for (const ws of sockets) {
|
|
186
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
153
187
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
154
188
|
if (topic.type !== "terminal" || topic.terminalId !== terminalId) continue
|
|
155
|
-
|
|
189
|
+
const envelope = createEnvelope(id, topic)
|
|
190
|
+
if (envelope.type !== "snapshot") continue
|
|
191
|
+
const signature = JSON.stringify(envelope.snapshot)
|
|
192
|
+
if (snapshotSignatures.get(id) === signature) continue
|
|
193
|
+
snapshotSignatures.set(id, signature)
|
|
194
|
+
send(ws, envelope)
|
|
156
195
|
}
|
|
157
196
|
}
|
|
158
197
|
}
|
|
@@ -177,18 +216,30 @@ export function createWsRouter({
|
|
|
177
216
|
|
|
178
217
|
const disposeKeybindingEvents = keybindings.onChange(() => {
|
|
179
218
|
for (const ws of sockets) {
|
|
219
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
180
220
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
181
221
|
if (topic.type !== "keybindings") continue
|
|
182
|
-
|
|
222
|
+
const envelope = createEnvelope(id, topic)
|
|
223
|
+
if (envelope.type !== "snapshot") continue
|
|
224
|
+
const signature = JSON.stringify(envelope.snapshot)
|
|
225
|
+
if (snapshotSignatures.get(id) === signature) continue
|
|
226
|
+
snapshotSignatures.set(id, signature)
|
|
227
|
+
send(ws, envelope)
|
|
183
228
|
}
|
|
184
229
|
}
|
|
185
230
|
})
|
|
186
231
|
|
|
187
232
|
const disposeUpdateEvents = updateManager?.onChange(() => {
|
|
188
233
|
for (const ws of sockets) {
|
|
234
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
189
235
|
for (const [id, topic] of ws.data.subscriptions.entries()) {
|
|
190
236
|
if (topic.type !== "update") continue
|
|
191
|
-
|
|
237
|
+
const envelope = createEnvelope(id, topic)
|
|
238
|
+
if (envelope.type !== "snapshot") continue
|
|
239
|
+
const signature = JSON.stringify(envelope.snapshot)
|
|
240
|
+
if (snapshotSignatures.get(id) === signature) continue
|
|
241
|
+
snapshotSignatures.set(id, signature)
|
|
242
|
+
send(ws, envelope)
|
|
192
243
|
}
|
|
193
244
|
}
|
|
194
245
|
}) ?? (() => {})
|
|
@@ -299,6 +350,60 @@ export function createWsRouter({
|
|
|
299
350
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
300
351
|
break
|
|
301
352
|
}
|
|
353
|
+
case "chat.refreshDiffs": {
|
|
354
|
+
const chat = store.getChat(command.chatId)
|
|
355
|
+
if (!chat) {
|
|
356
|
+
throw new Error("Chat not found")
|
|
357
|
+
}
|
|
358
|
+
const project = store.getProject(chat.projectId)
|
|
359
|
+
if (!project) {
|
|
360
|
+
throw new Error("Project not found")
|
|
361
|
+
}
|
|
362
|
+
const changed = await resolvedDiffStore.refreshSnapshot(command.chatId, project.localPath)
|
|
363
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
364
|
+
if (changed) {
|
|
365
|
+
broadcastSnapshots()
|
|
366
|
+
}
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
case "chat.generateCommitMessage": {
|
|
370
|
+
const chat = store.getChat(command.chatId)
|
|
371
|
+
if (!chat) {
|
|
372
|
+
throw new Error("Chat not found")
|
|
373
|
+
}
|
|
374
|
+
const project = store.getProject(chat.projectId)
|
|
375
|
+
if (!project) {
|
|
376
|
+
throw new Error("Project not found")
|
|
377
|
+
}
|
|
378
|
+
const result = await resolvedDiffStore.generateCommitMessage({
|
|
379
|
+
projectPath: project.localPath,
|
|
380
|
+
paths: command.paths,
|
|
381
|
+
})
|
|
382
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
case "chat.commitDiffs": {
|
|
386
|
+
const chat = store.getChat(command.chatId)
|
|
387
|
+
if (!chat) {
|
|
388
|
+
throw new Error("Chat not found")
|
|
389
|
+
}
|
|
390
|
+
const project = store.getProject(chat.projectId)
|
|
391
|
+
if (!project) {
|
|
392
|
+
throw new Error("Project not found")
|
|
393
|
+
}
|
|
394
|
+
const changed = await resolvedDiffStore.commitFiles({
|
|
395
|
+
chatId: command.chatId,
|
|
396
|
+
projectPath: project.localPath,
|
|
397
|
+
paths: command.paths,
|
|
398
|
+
summary: command.summary,
|
|
399
|
+
description: command.description,
|
|
400
|
+
})
|
|
401
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
402
|
+
if (changed) {
|
|
403
|
+
broadcastSnapshots()
|
|
404
|
+
}
|
|
405
|
+
return
|
|
406
|
+
}
|
|
302
407
|
case "chat.cancel": {
|
|
303
408
|
await agent.cancel(command.chatId)
|
|
304
409
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
@@ -350,6 +455,11 @@ export function createWsRouter({
|
|
|
350
455
|
broadcastSnapshots()
|
|
351
456
|
} catch (error) {
|
|
352
457
|
const messageText = error instanceof Error ? error.message : String(error)
|
|
458
|
+
console.error("[ws-router] command failed", {
|
|
459
|
+
id,
|
|
460
|
+
type: command.type,
|
|
461
|
+
message: messageText,
|
|
462
|
+
})
|
|
353
463
|
send(ws, { v: PROTOCOL_VERSION, type: "error", id, message: messageText })
|
|
354
464
|
}
|
|
355
465
|
}
|
|
@@ -377,20 +487,25 @@ export function createWsRouter({
|
|
|
377
487
|
}
|
|
378
488
|
|
|
379
489
|
if (parsed.type === "subscribe") {
|
|
490
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
380
491
|
ws.data.subscriptions.set(parsed.id, parsed.topic)
|
|
492
|
+
snapshotSignatures.delete(parsed.id)
|
|
381
493
|
if (parsed.topic.type === "local-projects") {
|
|
382
494
|
void refreshDiscovery().then(() => {
|
|
383
495
|
if (ws.data.subscriptions.has(parsed.id)) {
|
|
384
|
-
|
|
496
|
+
pushSnapshots(ws)
|
|
385
497
|
}
|
|
386
498
|
})
|
|
499
|
+
return
|
|
387
500
|
}
|
|
388
|
-
|
|
501
|
+
pushSnapshots(ws)
|
|
389
502
|
return
|
|
390
503
|
}
|
|
391
504
|
|
|
392
505
|
if (parsed.type === "unsubscribe") {
|
|
506
|
+
const snapshotSignatures = ensureSnapshotSignatures(ws)
|
|
393
507
|
ws.data.subscriptions.delete(parsed.id)
|
|
508
|
+
snapshotSignatures.delete(parsed.id)
|
|
394
509
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id: parsed.id })
|
|
395
510
|
return
|
|
396
511
|
}
|
package/src/shared/protocol.ts
CHANGED
|
@@ -75,6 +75,9 @@ export type ClientCommand =
|
|
|
75
75
|
effort?: string
|
|
76
76
|
planMode?: boolean
|
|
77
77
|
}
|
|
78
|
+
| { type: "chat.refreshDiffs"; chatId: string }
|
|
79
|
+
| { type: "chat.generateCommitMessage"; chatId: string; paths: string[] }
|
|
80
|
+
| { type: "chat.commitDiffs"; chatId: string; paths: string[]; summary: string; description?: string }
|
|
78
81
|
| { type: "chat.cancel"; chatId: string }
|
|
79
82
|
| { type: "chat.stopDraining"; chatId: string }
|
|
80
83
|
| { type: "chat.respondTool"; chatId: string; toolUseId: string; result: unknown }
|
package/src/shared/types.ts
CHANGED
|
@@ -171,6 +171,16 @@ export function resolveClaudeApiModelId(modelId: string, contextWindow?: ClaudeC
|
|
|
171
171
|
return contextWindow === "1m" ? `${modelId}[1m]` : modelId
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
export function resolveClaudeContextWindowTokens(contextWindow: ClaudeContextWindow): number {
|
|
175
|
+
switch (contextWindow) {
|
|
176
|
+
case "1m":
|
|
177
|
+
return 1_000_000
|
|
178
|
+
case "200k":
|
|
179
|
+
default:
|
|
180
|
+
return 200_000
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
174
184
|
export type KannaStatus =
|
|
175
185
|
| "idle"
|
|
176
186
|
| "starting"
|
|
@@ -440,6 +450,43 @@ export interface StatusEntry extends TranscriptEntryBase {
|
|
|
440
450
|
status: string
|
|
441
451
|
}
|
|
442
452
|
|
|
453
|
+
export interface ContextWindowUsageSnapshot {
|
|
454
|
+
usedTokens: number
|
|
455
|
+
totalProcessedTokens?: number
|
|
456
|
+
maxTokens?: number
|
|
457
|
+
inputTokens?: number
|
|
458
|
+
cachedInputTokens?: number
|
|
459
|
+
outputTokens?: number
|
|
460
|
+
reasoningOutputTokens?: number
|
|
461
|
+
lastUsedTokens?: number
|
|
462
|
+
lastInputTokens?: number
|
|
463
|
+
lastCachedInputTokens?: number
|
|
464
|
+
lastOutputTokens?: number
|
|
465
|
+
lastReasoningOutputTokens?: number
|
|
466
|
+
toolUses?: number
|
|
467
|
+
durationMs?: number
|
|
468
|
+
compactsAutomatically: boolean
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export interface ChatDiffFile {
|
|
472
|
+
path: string
|
|
473
|
+
changeType: "added" | "deleted" | "modified" | "renamed"
|
|
474
|
+
patch: string
|
|
475
|
+
mimeType?: string
|
|
476
|
+
size?: number
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export interface ChatDiffSnapshot {
|
|
480
|
+
status: "unknown" | "ready" | "no_repo"
|
|
481
|
+
branchName?: string
|
|
482
|
+
files: ChatDiffFile[]
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export interface ContextWindowUpdatedEntry extends TranscriptEntryBase {
|
|
486
|
+
kind: "context_window_updated"
|
|
487
|
+
usage: ContextWindowUsageSnapshot
|
|
488
|
+
}
|
|
489
|
+
|
|
443
490
|
export interface CompactBoundaryEntry extends TranscriptEntryBase {
|
|
444
491
|
kind: "compact_boundary"
|
|
445
492
|
}
|
|
@@ -466,6 +513,7 @@ export type TranscriptEntry =
|
|
|
466
513
|
| ToolResultEntry
|
|
467
514
|
| ResultEntry
|
|
468
515
|
| StatusEntry
|
|
516
|
+
| ContextWindowUpdatedEntry
|
|
469
517
|
| CompactBoundaryEntry
|
|
470
518
|
| CompactSummaryEntry
|
|
471
519
|
| ContextClearedEntry
|
|
@@ -579,6 +627,7 @@ export type HydratedTranscriptMessage =
|
|
|
579
627
|
| ({ kind: "assistant_text"; text: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
580
628
|
| ({ kind: "result"; success: boolean; cancelled?: boolean; result: string; durationMs: number; costUsd?: number; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
581
629
|
| ({ kind: "status"; status: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
630
|
+
| ({ kind: "context_window_updated"; usage: ContextWindowUsageSnapshot; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
582
631
|
| ({ kind: "compact_boundary"; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
583
632
|
| ({ kind: "compact_summary"; summary: string; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
584
633
|
| ({ kind: "context_cleared"; id: string; messageId?: string; timestamp: string; hidden?: boolean })
|
|
@@ -601,6 +650,7 @@ export interface ChatRuntime {
|
|
|
601
650
|
export interface ChatSnapshot {
|
|
602
651
|
runtime: ChatRuntime
|
|
603
652
|
messages: TranscriptEntry[]
|
|
653
|
+
diffs: ChatDiffSnapshot
|
|
604
654
|
availableProviders: ProviderCatalogEntry[]
|
|
605
655
|
}
|
|
606
656
|
|