kanna-code 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-B-8fHyaU.css +32 -0
- package/dist/client/assets/index-DrUC3AGz.js +2502 -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 +589 -1
- package/src/server/agent.ts +459 -22
- 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 +1 -1
- package/src/server/read-models.ts +6 -1
- 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.test.ts +5 -5
- package/src/server/ws-router.ts +125 -7
- package/src/shared/protocol.ts +4 -0
- package/src/shared/types.ts +51 -0
- package/dist/client/assets/index-C952_xJD.css +0 -32
- package/dist/client/assets/index-lgfU0zw2.js +0 -606
package/src/server/agent.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { query, type CanUseTool, type PermissionResult, type Query } from "@anthropic-ai/claude-agent-sdk"
|
|
1
|
+
import { query, type CanUseTool, type PermissionResult, type Query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"
|
|
2
2
|
import type {
|
|
3
3
|
AgentProvider,
|
|
4
4
|
ChatAttachment,
|
|
5
|
+
ContextWindowUsageSnapshot,
|
|
5
6
|
NormalizedToolCall,
|
|
6
7
|
PendingToolSnapshot,
|
|
7
8
|
KannaStatus,
|
|
@@ -64,11 +65,41 @@ interface ActiveTurn {
|
|
|
64
65
|
cancelRecorded: boolean
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
interface ClaudeSessionHandle {
|
|
69
|
+
provider: "claude"
|
|
70
|
+
stream: AsyncIterable<HarnessEvent>
|
|
71
|
+
getAccountInfo?: () => Promise<any>
|
|
72
|
+
interrupt: () => Promise<void>
|
|
73
|
+
close: () => void
|
|
74
|
+
sendPrompt: (content: string) => Promise<void>
|
|
75
|
+
setModel: (model: string) => Promise<void>
|
|
76
|
+
setPermissionMode: (planMode: boolean) => Promise<void>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ClaudeSessionState {
|
|
80
|
+
chatId: string
|
|
81
|
+
session: ClaudeSessionHandle
|
|
82
|
+
localPath: string
|
|
83
|
+
model: string
|
|
84
|
+
effort?: string
|
|
85
|
+
planMode: boolean
|
|
86
|
+
sessionToken: string | null
|
|
87
|
+
accountInfoLoaded: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
interface AgentCoordinatorArgs {
|
|
68
91
|
store: EventStore
|
|
69
92
|
onStateChange: () => void
|
|
70
93
|
codexManager?: CodexAppServerManager
|
|
71
94
|
generateTitle?: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
|
|
95
|
+
startClaudeSession?: (args: {
|
|
96
|
+
localPath: string
|
|
97
|
+
model: string
|
|
98
|
+
effort?: string
|
|
99
|
+
planMode: boolean
|
|
100
|
+
sessionToken: string | null
|
|
101
|
+
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
102
|
+
}) => Promise<ClaudeSessionHandle>
|
|
72
103
|
}
|
|
73
104
|
|
|
74
105
|
function timestamped<T extends Omit<TranscriptEntry, "_id" | "createdAt">>(
|
|
@@ -91,6 +122,14 @@ function stringFromUnknown(value: unknown) {
|
|
|
91
122
|
}
|
|
92
123
|
}
|
|
93
124
|
|
|
125
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
126
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function asNumber(value: unknown): number | undefined {
|
|
130
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
94
133
|
function escapeXmlAttribute(value: string) {
|
|
95
134
|
return value
|
|
96
135
|
.replaceAll("&", "&")
|
|
@@ -141,6 +180,72 @@ function discardedToolResult(
|
|
|
141
180
|
}
|
|
142
181
|
}
|
|
143
182
|
|
|
183
|
+
export function normalizeClaudeUsageSnapshot(
|
|
184
|
+
value: unknown,
|
|
185
|
+
maxTokens?: number,
|
|
186
|
+
): ContextWindowUsageSnapshot | null {
|
|
187
|
+
const usage = asRecord(value)
|
|
188
|
+
if (!usage) return null
|
|
189
|
+
|
|
190
|
+
const directInputTokens = asNumber(usage.input_tokens) ?? asNumber(usage.inputTokens) ?? 0
|
|
191
|
+
const cacheCreationInputTokens =
|
|
192
|
+
asNumber(usage.cache_creation_input_tokens) ?? asNumber(usage.cacheCreationInputTokens) ?? 0
|
|
193
|
+
const cacheReadInputTokens =
|
|
194
|
+
asNumber(usage.cache_read_input_tokens) ?? asNumber(usage.cacheReadInputTokens) ?? 0
|
|
195
|
+
const outputTokens = asNumber(usage.output_tokens) ?? asNumber(usage.outputTokens) ?? 0
|
|
196
|
+
const reasoningOutputTokens =
|
|
197
|
+
asNumber(usage.reasoning_output_tokens) ?? asNumber(usage.reasoningOutputTokens)
|
|
198
|
+
const toolUses = asNumber(usage.tool_uses) ?? asNumber(usage.toolUses)
|
|
199
|
+
const durationMs = asNumber(usage.duration_ms) ?? asNumber(usage.durationMs)
|
|
200
|
+
|
|
201
|
+
const inputTokens = directInputTokens + cacheCreationInputTokens + cacheReadInputTokens
|
|
202
|
+
const usedTokens = inputTokens + outputTokens
|
|
203
|
+
if (usedTokens <= 0) {
|
|
204
|
+
return null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
usedTokens,
|
|
209
|
+
inputTokens,
|
|
210
|
+
...(cacheReadInputTokens > 0 ? { cachedInputTokens: cacheReadInputTokens } : {}),
|
|
211
|
+
...(outputTokens > 0 ? { outputTokens } : {}),
|
|
212
|
+
...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
|
|
213
|
+
lastUsedTokens: usedTokens,
|
|
214
|
+
lastInputTokens: inputTokens,
|
|
215
|
+
...(cacheReadInputTokens > 0 ? { lastCachedInputTokens: cacheReadInputTokens } : {}),
|
|
216
|
+
...(outputTokens > 0 ? { lastOutputTokens: outputTokens } : {}),
|
|
217
|
+
...(reasoningOutputTokens !== undefined ? { lastReasoningOutputTokens: reasoningOutputTokens } : {}),
|
|
218
|
+
...(toolUses !== undefined ? { toolUses } : {}),
|
|
219
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
220
|
+
...(typeof maxTokens === "number" && maxTokens > 0 ? { maxTokens } : {}),
|
|
221
|
+
compactsAutomatically: false,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function maxClaudeContextWindowFromModelUsage(modelUsage: unknown): number | undefined {
|
|
226
|
+
const record = asRecord(modelUsage)
|
|
227
|
+
if (!record) return undefined
|
|
228
|
+
|
|
229
|
+
let maxContextWindow: number | undefined
|
|
230
|
+
for (const value of Object.values(record)) {
|
|
231
|
+
const usage = asRecord(value)
|
|
232
|
+
const contextWindow = asNumber(usage?.contextWindow) ?? asNumber(usage?.context_window)
|
|
233
|
+
if (contextWindow === undefined) continue
|
|
234
|
+
maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow)
|
|
235
|
+
}
|
|
236
|
+
return maxContextWindow
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getClaudeAssistantMessageUsageId(message: any): string | null {
|
|
240
|
+
if (typeof message?.message?.id === "string" && message.message.id) {
|
|
241
|
+
return message.message.id
|
|
242
|
+
}
|
|
243
|
+
if (typeof message?.uuid === "string" && message.uuid) {
|
|
244
|
+
return message.uuid
|
|
245
|
+
}
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
144
249
|
export function normalizeClaudeStreamMessage(message: any): TranscriptEntry[] {
|
|
145
250
|
const debugRaw = JSON.stringify(message)
|
|
146
251
|
const messageId = typeof message.uuid === "string" ? message.uuid : undefined
|
|
@@ -258,26 +363,129 @@ export function normalizeClaudeStreamMessage(message: any): TranscriptEntry[] {
|
|
|
258
363
|
}
|
|
259
364
|
|
|
260
365
|
async function* createClaudeHarnessStream(q: Query): AsyncGenerator<HarnessEvent> {
|
|
366
|
+
let seenAssistantUsageIds = new Set<string>()
|
|
367
|
+
let latestUsageSnapshot: ContextWindowUsageSnapshot | null = null
|
|
368
|
+
let lastKnownContextWindow: number | undefined
|
|
369
|
+
|
|
261
370
|
for await (const sdkMessage of q as AsyncIterable<any>) {
|
|
262
371
|
const sessionToken = typeof sdkMessage.session_id === "string" ? sdkMessage.session_id : null
|
|
263
372
|
if (sessionToken) {
|
|
264
373
|
yield { type: "session_token", sessionToken }
|
|
265
374
|
}
|
|
375
|
+
|
|
376
|
+
if (sdkMessage?.type === "assistant") {
|
|
377
|
+
const usageId = getClaudeAssistantMessageUsageId(sdkMessage)
|
|
378
|
+
const usageSnapshot = normalizeClaudeUsageSnapshot(sdkMessage.usage, lastKnownContextWindow)
|
|
379
|
+
if (usageId && usageSnapshot && !seenAssistantUsageIds.has(usageId)) {
|
|
380
|
+
seenAssistantUsageIds.add(usageId)
|
|
381
|
+
latestUsageSnapshot = usageSnapshot
|
|
382
|
+
yield {
|
|
383
|
+
type: "transcript",
|
|
384
|
+
entry: timestamped({
|
|
385
|
+
kind: "context_window_updated",
|
|
386
|
+
usage: usageSnapshot,
|
|
387
|
+
}),
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (sdkMessage?.type === "result") {
|
|
393
|
+
const resultContextWindow = maxClaudeContextWindowFromModelUsage(sdkMessage.modelUsage)
|
|
394
|
+
if (resultContextWindow !== undefined) {
|
|
395
|
+
lastKnownContextWindow = resultContextWindow
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const accumulatedUsage = normalizeClaudeUsageSnapshot(
|
|
399
|
+
sdkMessage.usage,
|
|
400
|
+
resultContextWindow ?? lastKnownContextWindow,
|
|
401
|
+
)
|
|
402
|
+
const finalUsage = latestUsageSnapshot
|
|
403
|
+
? {
|
|
404
|
+
...latestUsageSnapshot,
|
|
405
|
+
...(typeof (resultContextWindow ?? lastKnownContextWindow) === "number"
|
|
406
|
+
? { maxTokens: resultContextWindow ?? lastKnownContextWindow }
|
|
407
|
+
: {}),
|
|
408
|
+
...(accumulatedUsage && accumulatedUsage.usedTokens > latestUsageSnapshot.usedTokens
|
|
409
|
+
? { totalProcessedTokens: accumulatedUsage.usedTokens }
|
|
410
|
+
: {}),
|
|
411
|
+
}
|
|
412
|
+
: accumulatedUsage
|
|
413
|
+
|
|
414
|
+
if (finalUsage) {
|
|
415
|
+
yield {
|
|
416
|
+
type: "transcript",
|
|
417
|
+
entry: timestamped({
|
|
418
|
+
kind: "context_window_updated",
|
|
419
|
+
usage: finalUsage,
|
|
420
|
+
}),
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
seenAssistantUsageIds = new Set<string>()
|
|
425
|
+
latestUsageSnapshot = null
|
|
426
|
+
}
|
|
427
|
+
|
|
266
428
|
for (const entry of normalizeClaudeStreamMessage(sdkMessage)) {
|
|
267
429
|
yield { type: "transcript", entry }
|
|
268
430
|
}
|
|
269
431
|
}
|
|
270
432
|
}
|
|
271
433
|
|
|
272
|
-
|
|
273
|
-
|
|
434
|
+
class AsyncMessageQueue<T> implements AsyncIterable<T> {
|
|
435
|
+
private readonly values: T[] = []
|
|
436
|
+
private readonly waiters: Array<(result: IteratorResult<T>) => void> = []
|
|
437
|
+
private closed = false
|
|
438
|
+
|
|
439
|
+
push(value: T) {
|
|
440
|
+
if (this.closed) {
|
|
441
|
+
throw new Error("Cannot push to a closed queue")
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const waiter = this.waiters.shift()
|
|
445
|
+
if (waiter) {
|
|
446
|
+
waiter({ done: false, value })
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.values.push(value)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
close() {
|
|
454
|
+
if (this.closed) return
|
|
455
|
+
this.closed = true
|
|
456
|
+
while (this.waiters.length > 0) {
|
|
457
|
+
const waiter = this.waiters.shift()
|
|
458
|
+
waiter?.({ done: true, value: undefined as never })
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
463
|
+
return {
|
|
464
|
+
next: async () => {
|
|
465
|
+
if (this.values.length > 0) {
|
|
466
|
+
return { done: false, value: this.values.shift() as T }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (this.closed) {
|
|
470
|
+
return { done: true, value: undefined as never }
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return await new Promise<IteratorResult<T>>((resolve) => {
|
|
474
|
+
this.waiters.push(resolve)
|
|
475
|
+
})
|
|
476
|
+
},
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function startClaudeSession(args: {
|
|
274
482
|
localPath: string
|
|
275
483
|
model: string
|
|
276
484
|
effort?: string
|
|
277
485
|
planMode: boolean
|
|
278
486
|
sessionToken: string | null
|
|
279
487
|
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
280
|
-
}): Promise<
|
|
488
|
+
}): Promise<ClaudeSessionHandle> {
|
|
281
489
|
const canUseTool: CanUseTool = async (toolName, input, options) => {
|
|
282
490
|
if (toolName !== "AskUserQuestion" && toolName !== "ExitPlanMode") {
|
|
283
491
|
return {
|
|
@@ -333,8 +541,10 @@ async function startClaudeTurn(args: {
|
|
|
333
541
|
} satisfies PermissionResult
|
|
334
542
|
}
|
|
335
543
|
|
|
544
|
+
const promptQueue = new AsyncMessageQueue<SDKUserMessage>()
|
|
545
|
+
|
|
336
546
|
const q = query({
|
|
337
|
-
prompt:
|
|
547
|
+
prompt: promptQueue,
|
|
338
548
|
options: {
|
|
339
549
|
cwd: args.localPath,
|
|
340
550
|
model: args.model,
|
|
@@ -361,7 +571,25 @@ async function startClaudeTurn(args: {
|
|
|
361
571
|
interrupt: async () => {
|
|
362
572
|
await q.interrupt()
|
|
363
573
|
},
|
|
574
|
+
sendPrompt: async (content: string) => {
|
|
575
|
+
promptQueue.push({
|
|
576
|
+
type: "user",
|
|
577
|
+
message: {
|
|
578
|
+
role: "user",
|
|
579
|
+
content,
|
|
580
|
+
},
|
|
581
|
+
parent_tool_use_id: null,
|
|
582
|
+
session_id: args.sessionToken ?? "",
|
|
583
|
+
})
|
|
584
|
+
},
|
|
585
|
+
setModel: async (model: string) => {
|
|
586
|
+
await q.setModel(model)
|
|
587
|
+
},
|
|
588
|
+
setPermissionMode: async (planMode: boolean) => {
|
|
589
|
+
await q.setPermissionMode(planMode ? "plan" : "acceptEdits")
|
|
590
|
+
},
|
|
364
591
|
close: () => {
|
|
592
|
+
promptQueue.close()
|
|
365
593
|
q.close()
|
|
366
594
|
},
|
|
367
595
|
}
|
|
@@ -372,14 +600,18 @@ export class AgentCoordinator {
|
|
|
372
600
|
private readonly onStateChange: () => void
|
|
373
601
|
private readonly codexManager: CodexAppServerManager
|
|
374
602
|
private readonly generateTitle: (messageContent: string, cwd: string) => Promise<GenerateChatTitleResult>
|
|
603
|
+
private readonly startClaudeSessionFn: NonNullable<AgentCoordinatorArgs["startClaudeSession"]>
|
|
375
604
|
private reportBackgroundError: ((message: string) => void) | null = null
|
|
376
605
|
readonly activeTurns = new Map<string, ActiveTurn>()
|
|
606
|
+
readonly drainingStreams = new Map<string, { turn: HarnessTurn }>()
|
|
607
|
+
readonly claudeSessions = new Map<string, ClaudeSessionState>()
|
|
377
608
|
|
|
378
609
|
constructor(args: AgentCoordinatorArgs) {
|
|
379
610
|
this.store = args.store
|
|
380
611
|
this.onStateChange = args.onStateChange
|
|
381
612
|
this.codexManager = args.codexManager ?? new CodexAppServerManager()
|
|
382
613
|
this.generateTitle = args.generateTitle ?? generateTitleForChatDetailed
|
|
614
|
+
this.startClaudeSessionFn = args.startClaudeSession ?? startClaudeSession
|
|
383
615
|
}
|
|
384
616
|
|
|
385
617
|
setBackgroundErrorReporter(report: ((message: string) => void) | null) {
|
|
@@ -400,6 +632,28 @@ export class AgentCoordinator {
|
|
|
400
632
|
return { toolUseId: pending.toolUseId, toolKind: pending.tool.toolKind }
|
|
401
633
|
}
|
|
402
634
|
|
|
635
|
+
getDrainingChatIds(): Set<string> {
|
|
636
|
+
return new Set(this.drainingStreams.keys())
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async stopDraining(chatId: string) {
|
|
640
|
+
const draining = this.drainingStreams.get(chatId)
|
|
641
|
+
if (!draining) return
|
|
642
|
+
draining.turn.close()
|
|
643
|
+
this.drainingStreams.delete(chatId)
|
|
644
|
+
this.onStateChange()
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async closeChat(chatId: string) {
|
|
648
|
+
await this.stopDraining(chatId)
|
|
649
|
+
const claudeSession = this.claudeSessions.get(chatId)
|
|
650
|
+
if (claudeSession) {
|
|
651
|
+
claudeSession.session.close()
|
|
652
|
+
this.claudeSessions.delete(chatId)
|
|
653
|
+
}
|
|
654
|
+
this.onStateChange()
|
|
655
|
+
}
|
|
656
|
+
|
|
403
657
|
private resolveProvider(command: Extract<ClientCommand, { type: "chat.send" }>, currentProvider: AgentProvider | null) {
|
|
404
658
|
if (currentProvider) return currentProvider
|
|
405
659
|
return command.provider ?? "claude"
|
|
@@ -438,6 +692,13 @@ export class AgentCoordinator {
|
|
|
438
692
|
planMode: boolean
|
|
439
693
|
appendUserPrompt: boolean
|
|
440
694
|
}) {
|
|
695
|
+
// Close any lingering draining stream before starting a new turn.
|
|
696
|
+
const draining = this.drainingStreams.get(args.chatId)
|
|
697
|
+
if (draining) {
|
|
698
|
+
draining.turn.close()
|
|
699
|
+
this.drainingStreams.delete(args.chatId)
|
|
700
|
+
}
|
|
701
|
+
|
|
441
702
|
const chat = this.store.requireChat(args.chatId)
|
|
442
703
|
if (this.activeTurns.has(args.chatId)) {
|
|
443
704
|
throw new Error("Chat is already running")
|
|
@@ -456,19 +717,20 @@ export class AgentCoordinator {
|
|
|
456
717
|
await this.store.renameChat(args.chatId, optimisticTitle)
|
|
457
718
|
}
|
|
458
719
|
|
|
459
|
-
if (args.appendUserPrompt) {
|
|
460
|
-
await this.store.appendMessage(
|
|
461
|
-
args.chatId,
|
|
462
|
-
timestamped({ kind: "user_prompt", content: args.content, attachments: args.attachments }, Date.now())
|
|
463
|
-
)
|
|
464
|
-
}
|
|
465
|
-
await this.store.recordTurnStarted(args.chatId)
|
|
466
|
-
|
|
467
720
|
const project = this.store.getProject(chat.projectId)
|
|
468
721
|
if (!project) {
|
|
469
722
|
throw new Error("Project not found")
|
|
470
723
|
}
|
|
471
724
|
|
|
725
|
+
if (args.appendUserPrompt) {
|
|
726
|
+
const userPromptEntry = timestamped(
|
|
727
|
+
{ kind: "user_prompt", content: args.content, attachments: args.attachments },
|
|
728
|
+
Date.now()
|
|
729
|
+
)
|
|
730
|
+
await this.store.appendMessage(args.chatId, userPromptEntry)
|
|
731
|
+
}
|
|
732
|
+
await this.store.recordTurnStarted(args.chatId)
|
|
733
|
+
|
|
472
734
|
if (shouldGenerateTitle) {
|
|
473
735
|
void this.generateTitleInBackground(args.chatId, args.content, project.localPath, optimisticTitle ?? "New Chat")
|
|
474
736
|
}
|
|
@@ -493,8 +755,8 @@ export class AgentCoordinator {
|
|
|
493
755
|
|
|
494
756
|
let turn: HarnessTurn
|
|
495
757
|
if (args.provider === "claude") {
|
|
496
|
-
turn = await startClaudeTurn({
|
|
497
|
-
|
|
758
|
+
turn = await this.startClaudeTurn({
|
|
759
|
+
chatId: args.chatId,
|
|
498
760
|
localPath: project.localPath,
|
|
499
761
|
model: args.model,
|
|
500
762
|
effort: args.effort,
|
|
@@ -529,7 +791,7 @@ export class AgentCoordinator {
|
|
|
529
791
|
effort: args.effort,
|
|
530
792
|
serviceTier: args.serviceTier,
|
|
531
793
|
planMode: args.planMode,
|
|
532
|
-
status: "starting",
|
|
794
|
+
status: args.provider === "claude" ? "running" : "starting",
|
|
533
795
|
pendingTool: null,
|
|
534
796
|
postToolFollowUp: null,
|
|
535
797
|
hasFinalResult: false,
|
|
@@ -543,15 +805,93 @@ export class AgentCoordinator {
|
|
|
543
805
|
void turn.getAccountInfo()
|
|
544
806
|
.then(async (accountInfo) => {
|
|
545
807
|
if (!accountInfo) return
|
|
808
|
+
if (args.provider === "claude") {
|
|
809
|
+
const session = this.claudeSessions.get(args.chatId)
|
|
810
|
+
if (session) {
|
|
811
|
+
if (session.accountInfoLoaded) return
|
|
812
|
+
session.accountInfoLoaded = true
|
|
813
|
+
} else {
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
}
|
|
546
817
|
await this.store.appendMessage(args.chatId, timestamped({ kind: "account_info", accountInfo }))
|
|
547
818
|
this.onStateChange()
|
|
548
819
|
})
|
|
549
820
|
.catch(() => undefined)
|
|
550
821
|
}
|
|
551
822
|
|
|
823
|
+
if (args.provider === "claude") {
|
|
824
|
+
const session = this.claudeSessions.get(args.chatId)
|
|
825
|
+
if (!session) {
|
|
826
|
+
throw new Error("Claude session was not initialized")
|
|
827
|
+
}
|
|
828
|
+
await session.session.sendPrompt(buildPromptText(args.content, args.attachments))
|
|
829
|
+
return
|
|
830
|
+
}
|
|
831
|
+
|
|
552
832
|
void this.runTurn(active)
|
|
553
833
|
}
|
|
554
834
|
|
|
835
|
+
private async startClaudeTurn(args: {
|
|
836
|
+
chatId: string
|
|
837
|
+
localPath: string
|
|
838
|
+
model: string
|
|
839
|
+
effort?: string
|
|
840
|
+
planMode: boolean
|
|
841
|
+
sessionToken: string | null
|
|
842
|
+
onToolRequest: (request: HarnessToolRequest) => Promise<unknown>
|
|
843
|
+
}): Promise<HarnessTurn> {
|
|
844
|
+
let session = this.claudeSessions.get(args.chatId)
|
|
845
|
+
|
|
846
|
+
if (!session || session.localPath !== args.localPath || session.effort !== args.effort) {
|
|
847
|
+
if (session) {
|
|
848
|
+
session.session.close()
|
|
849
|
+
this.claudeSessions.delete(args.chatId)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const started = await this.startClaudeSessionFn({
|
|
853
|
+
localPath: args.localPath,
|
|
854
|
+
model: args.model,
|
|
855
|
+
effort: args.effort,
|
|
856
|
+
planMode: args.planMode,
|
|
857
|
+
sessionToken: args.sessionToken,
|
|
858
|
+
onToolRequest: args.onToolRequest,
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
session = {
|
|
862
|
+
chatId: args.chatId,
|
|
863
|
+
session: started,
|
|
864
|
+
localPath: args.localPath,
|
|
865
|
+
model: args.model,
|
|
866
|
+
effort: args.effort,
|
|
867
|
+
planMode: args.planMode,
|
|
868
|
+
sessionToken: args.sessionToken,
|
|
869
|
+
accountInfoLoaded: false,
|
|
870
|
+
}
|
|
871
|
+
this.claudeSessions.set(args.chatId, session)
|
|
872
|
+
void this.runClaudeSession(session)
|
|
873
|
+
} else {
|
|
874
|
+
if (session.model !== args.model) {
|
|
875
|
+
await session.session.setModel(args.model)
|
|
876
|
+
session.model = args.model
|
|
877
|
+
}
|
|
878
|
+
if (session.planMode !== args.planMode) {
|
|
879
|
+
await session.session.setPermissionMode(args.planMode)
|
|
880
|
+
session.planMode = args.planMode
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return {
|
|
885
|
+
provider: "claude",
|
|
886
|
+
stream: {
|
|
887
|
+
async *[Symbol.asyncIterator]() {},
|
|
888
|
+
},
|
|
889
|
+
getAccountInfo: session.session.getAccountInfo,
|
|
890
|
+
interrupt: session.session.interrupt,
|
|
891
|
+
close: () => {},
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
555
895
|
async send(command: Extract<ClientCommand, { type: "chat.send" }>) {
|
|
556
896
|
let chatId = command.chatId
|
|
557
897
|
|
|
@@ -581,6 +921,66 @@ export class AgentCoordinator {
|
|
|
581
921
|
return { chatId }
|
|
582
922
|
}
|
|
583
923
|
|
|
924
|
+
private async runClaudeSession(session: ClaudeSessionState) {
|
|
925
|
+
try {
|
|
926
|
+
for await (const event of session.session.stream) {
|
|
927
|
+
if (event.type === "session_token" && event.sessionToken) {
|
|
928
|
+
session.sessionToken = event.sessionToken
|
|
929
|
+
await this.store.setSessionToken(session.chatId, event.sessionToken)
|
|
930
|
+
this.onStateChange()
|
|
931
|
+
continue
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (!event.entry) continue
|
|
935
|
+
await this.store.appendMessage(session.chatId, event.entry)
|
|
936
|
+
|
|
937
|
+
const active = this.activeTurns.get(session.chatId)
|
|
938
|
+
if (event.entry.kind === "system_init" && active) {
|
|
939
|
+
active.status = "running"
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (event.entry.kind === "result" && active) {
|
|
943
|
+
active.hasFinalResult = true
|
|
944
|
+
if (event.entry.isError) {
|
|
945
|
+
await this.store.recordTurnFailed(session.chatId, event.entry.result || "Turn failed")
|
|
946
|
+
} else if (!active.cancelRequested) {
|
|
947
|
+
await this.store.recordTurnFinished(session.chatId)
|
|
948
|
+
}
|
|
949
|
+
this.activeTurns.delete(session.chatId)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
this.onStateChange()
|
|
953
|
+
}
|
|
954
|
+
} catch (error) {
|
|
955
|
+
const active = this.activeTurns.get(session.chatId)
|
|
956
|
+
if (active && !active.cancelRequested) {
|
|
957
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
958
|
+
await this.store.appendMessage(
|
|
959
|
+
session.chatId,
|
|
960
|
+
timestamped({
|
|
961
|
+
kind: "result",
|
|
962
|
+
subtype: "error",
|
|
963
|
+
isError: true,
|
|
964
|
+
durationMs: 0,
|
|
965
|
+
result: message,
|
|
966
|
+
})
|
|
967
|
+
)
|
|
968
|
+
await this.store.recordTurnFailed(session.chatId, message)
|
|
969
|
+
}
|
|
970
|
+
} finally {
|
|
971
|
+
this.claudeSessions.delete(session.chatId)
|
|
972
|
+
const active = this.activeTurns.get(session.chatId)
|
|
973
|
+
if (active?.provider === "claude") {
|
|
974
|
+
if (active.cancelRequested && !active.cancelRecorded) {
|
|
975
|
+
await this.store.recordTurnCancelled(session.chatId)
|
|
976
|
+
}
|
|
977
|
+
this.activeTurns.delete(session.chatId)
|
|
978
|
+
}
|
|
979
|
+
session.session.close()
|
|
980
|
+
this.onStateChange()
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
584
984
|
private async generateTitleInBackground(chatId: string, messageContent: string, cwd: string, expectedCurrentTitle: string) {
|
|
585
985
|
try {
|
|
586
986
|
const result = await this.generateTitle(messageContent, cwd)
|
|
@@ -607,6 +1007,10 @@ export class AgentCoordinator {
|
|
|
607
1007
|
private async runTurn(active: ActiveTurn) {
|
|
608
1008
|
try {
|
|
609
1009
|
for await (const event of active.turn.stream) {
|
|
1010
|
+
// Once cancelled, stop processing further stream events.
|
|
1011
|
+
// cancel() already removed us from activeTurns and notified the UI.
|
|
1012
|
+
if (active.cancelRequested) break
|
|
1013
|
+
|
|
610
1014
|
if (event.type === "session_token" && event.sessionToken) {
|
|
611
1015
|
await this.store.setSessionToken(active.chatId, event.sessionToken)
|
|
612
1016
|
this.onStateChange()
|
|
@@ -627,6 +1031,14 @@ export class AgentCoordinator {
|
|
|
627
1031
|
} else if (!active.cancelRequested) {
|
|
628
1032
|
await this.store.recordTurnFinished(active.chatId)
|
|
629
1033
|
}
|
|
1034
|
+
// Remove from activeTurns as soon as the result arrives so the UI
|
|
1035
|
+
// transitions to idle immediately. The stream may still be open
|
|
1036
|
+
// (e.g. background tasks), but the user should be able to send
|
|
1037
|
+
// new messages without having to hit stop first.
|
|
1038
|
+
this.activeTurns.delete(active.chatId)
|
|
1039
|
+
// Track the still-open stream so the UI can show a draining
|
|
1040
|
+
// indicator and the user can stop background tasks.
|
|
1041
|
+
this.drainingStreams.set(active.chatId, { turn: active.turn })
|
|
630
1042
|
}
|
|
631
1043
|
|
|
632
1044
|
this.onStateChange()
|
|
@@ -651,7 +1063,14 @@ export class AgentCoordinator {
|
|
|
651
1063
|
await this.store.recordTurnCancelled(active.chatId)
|
|
652
1064
|
}
|
|
653
1065
|
active.turn.close()
|
|
654
|
-
this.
|
|
1066
|
+
// Only remove if we're still the active turn for this chat.
|
|
1067
|
+
// We may have already been removed by result handling or cancel(),
|
|
1068
|
+
// and a new turn may have started for the same chatId.
|
|
1069
|
+
if (this.activeTurns.get(active.chatId) === active) {
|
|
1070
|
+
this.activeTurns.delete(active.chatId)
|
|
1071
|
+
}
|
|
1072
|
+
// Stream has fully ended — no longer draining.
|
|
1073
|
+
this.drainingStreams.delete(active.chatId)
|
|
655
1074
|
this.onStateChange()
|
|
656
1075
|
|
|
657
1076
|
if (active.postToolFollowUp && !active.cancelRequested) {
|
|
@@ -687,9 +1106,18 @@ export class AgentCoordinator {
|
|
|
687
1106
|
}
|
|
688
1107
|
|
|
689
1108
|
async cancel(chatId: string) {
|
|
1109
|
+
// Also clean up any draining stream for this chat.
|
|
1110
|
+
const draining = this.drainingStreams.get(chatId)
|
|
1111
|
+
if (draining) {
|
|
1112
|
+
draining.turn.close()
|
|
1113
|
+
this.drainingStreams.delete(chatId)
|
|
1114
|
+
}
|
|
1115
|
+
|
|
690
1116
|
const active = this.activeTurns.get(chatId)
|
|
691
1117
|
if (!active) return
|
|
692
1118
|
|
|
1119
|
+
// Guard against concurrent cancel() calls — only the first one does work.
|
|
1120
|
+
if (active.cancelRequested) return
|
|
693
1121
|
active.cancelRequested = true
|
|
694
1122
|
|
|
695
1123
|
const pendingTool = active.pendingTool
|
|
@@ -715,14 +1143,23 @@ export class AgentCoordinator {
|
|
|
715
1143
|
active.cancelRecorded = true
|
|
716
1144
|
active.hasFinalResult = true
|
|
717
1145
|
|
|
1146
|
+
// Remove from activeTurns immediately so the UI reflects the cancellation
|
|
1147
|
+
// right away, rather than waiting for interrupt() which may hang.
|
|
1148
|
+
this.activeTurns.delete(chatId)
|
|
1149
|
+
this.onStateChange()
|
|
1150
|
+
|
|
1151
|
+
// Now attempt to interrupt/close the underlying stream in the background.
|
|
1152
|
+
// This is best-effort — the turn is already removed from active state above,
|
|
1153
|
+
// and runTurn()'s finally block will also call close().
|
|
718
1154
|
try {
|
|
719
|
-
await
|
|
1155
|
+
await Promise.race([
|
|
1156
|
+
active.turn.interrupt(),
|
|
1157
|
+
new Promise((resolve) => setTimeout(resolve, 5_000)),
|
|
1158
|
+
])
|
|
720
1159
|
} catch {
|
|
721
|
-
|
|
1160
|
+
// interrupt() failed — force close
|
|
722
1161
|
}
|
|
723
|
-
|
|
724
|
-
this.activeTurns.delete(chatId)
|
|
725
|
-
this.onStateChange()
|
|
1162
|
+
active.turn.close()
|
|
726
1163
|
}
|
|
727
1164
|
|
|
728
1165
|
async respondTool(command: Extract<ClientCommand, { type: "chat.respondTool" }>) {
|