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.test.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AgentCoordinator,
|
|
4
|
+
buildAttachmentHintText,
|
|
5
|
+
buildPromptText,
|
|
6
|
+
maxClaudeContextWindowFromModelUsage,
|
|
7
|
+
normalizeClaudeStreamMessage,
|
|
8
|
+
normalizeClaudeUsageSnapshot,
|
|
9
|
+
} from "./agent"
|
|
3
10
|
import type { HarnessTurn } from "./harness-types"
|
|
4
11
|
import type { ChatAttachment, TranscriptEntry } from "../shared/types"
|
|
5
12
|
|
|
@@ -21,6 +28,44 @@ async function waitFor(condition: () => boolean, timeoutMs = 2000) {
|
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
class AsyncEventQueue<T> implements AsyncIterable<T> {
|
|
32
|
+
private readonly values: T[] = []
|
|
33
|
+
private readonly waiters: Array<(result: IteratorResult<T>) => void> = []
|
|
34
|
+
private closed = false
|
|
35
|
+
|
|
36
|
+
push(value: T) {
|
|
37
|
+
const waiter = this.waiters.shift()
|
|
38
|
+
if (waiter) {
|
|
39
|
+
waiter({ done: false, value })
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
this.values.push(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
close() {
|
|
46
|
+
this.closed = true
|
|
47
|
+
while (this.waiters.length > 0) {
|
|
48
|
+
this.waiters.shift()?.({ done: true, value: undefined as never })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
53
|
+
return {
|
|
54
|
+
next: async () => {
|
|
55
|
+
if (this.values.length > 0) {
|
|
56
|
+
return { done: false, value: this.values.shift() as T }
|
|
57
|
+
}
|
|
58
|
+
if (this.closed) {
|
|
59
|
+
return { done: true, value: undefined as never }
|
|
60
|
+
}
|
|
61
|
+
return await new Promise<IteratorResult<T>>((resolve) => {
|
|
62
|
+
this.waiters.push(resolve)
|
|
63
|
+
})
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
describe("normalizeClaudeStreamMessage", () => {
|
|
25
70
|
test("normalizes assistant tool calls", () => {
|
|
26
71
|
const entries = normalizeClaudeStreamMessage({
|
|
@@ -61,6 +106,43 @@ describe("normalizeClaudeStreamMessage", () => {
|
|
|
61
106
|
if (entries[0]?.kind !== "result") throw new Error("unexpected entry")
|
|
62
107
|
expect(entries[0].durationMs).toBe(3210)
|
|
63
108
|
})
|
|
109
|
+
|
|
110
|
+
test("normalizes Claude usage snapshots from SDK usage payloads", () => {
|
|
111
|
+
const snapshot = normalizeClaudeUsageSnapshot({
|
|
112
|
+
input_tokens: 4,
|
|
113
|
+
cache_creation_input_tokens: 2715,
|
|
114
|
+
cache_read_input_tokens: 21144,
|
|
115
|
+
output_tokens: 679,
|
|
116
|
+
tool_uses: 2,
|
|
117
|
+
duration_ms: 654,
|
|
118
|
+
}, 200_000)
|
|
119
|
+
|
|
120
|
+
expect(snapshot).toEqual({
|
|
121
|
+
usedTokens: 24_542,
|
|
122
|
+
inputTokens: 23_863,
|
|
123
|
+
cachedInputTokens: 21_144,
|
|
124
|
+
outputTokens: 679,
|
|
125
|
+
lastUsedTokens: 24_542,
|
|
126
|
+
lastInputTokens: 23_863,
|
|
127
|
+
lastCachedInputTokens: 21_144,
|
|
128
|
+
lastOutputTokens: 679,
|
|
129
|
+
toolUses: 2,
|
|
130
|
+
durationMs: 654,
|
|
131
|
+
maxTokens: 200_000,
|
|
132
|
+
compactsAutomatically: false,
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("reads the max Claude context window from modelUsage", () => {
|
|
137
|
+
expect(maxClaudeContextWindowFromModelUsage({
|
|
138
|
+
"claude-opus-4-6": {
|
|
139
|
+
contextWindow: 200_000,
|
|
140
|
+
},
|
|
141
|
+
"claude-opus-4-6[1m]": {
|
|
142
|
+
contextWindow: 1_000_000,
|
|
143
|
+
},
|
|
144
|
+
})).toBe(1_000_000)
|
|
145
|
+
})
|
|
64
146
|
})
|
|
65
147
|
|
|
66
148
|
describe("attachment prompt helpers", () => {
|
|
@@ -706,6 +788,370 @@ describe("AgentCoordinator codex integration", () => {
|
|
|
706
788
|
expect(store.messages.some((entry) => entry.kind === "interrupted")).toBe(true)
|
|
707
789
|
})
|
|
708
790
|
|
|
791
|
+
test("UI unblocks immediately when result arrives even if stream stays open", async () => {
|
|
792
|
+
let resolveStream!: () => void
|
|
793
|
+
|
|
794
|
+
const fakeCodexManager = {
|
|
795
|
+
async startSession() {},
|
|
796
|
+
async startTurn(): Promise<HarnessTurn> {
|
|
797
|
+
async function* stream() {
|
|
798
|
+
yield {
|
|
799
|
+
type: "transcript" as const,
|
|
800
|
+
entry: timestamped({
|
|
801
|
+
kind: "system_init",
|
|
802
|
+
provider: "codex",
|
|
803
|
+
model: "gpt-5.4",
|
|
804
|
+
tools: [],
|
|
805
|
+
agents: [],
|
|
806
|
+
slashCommands: [],
|
|
807
|
+
mcpServers: [],
|
|
808
|
+
}),
|
|
809
|
+
}
|
|
810
|
+
// Produce the result event
|
|
811
|
+
yield {
|
|
812
|
+
type: "transcript" as const,
|
|
813
|
+
entry: timestamped({
|
|
814
|
+
kind: "result",
|
|
815
|
+
subtype: "success",
|
|
816
|
+
isError: false,
|
|
817
|
+
durationMs: 120_000,
|
|
818
|
+
result: "done",
|
|
819
|
+
}),
|
|
820
|
+
}
|
|
821
|
+
// Stream stays open (simulates background tasks still running)
|
|
822
|
+
await new Promise<void>((resolve) => {
|
|
823
|
+
resolveStream = resolve
|
|
824
|
+
})
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
provider: "codex",
|
|
829
|
+
stream: stream(),
|
|
830
|
+
interrupt: async () => {},
|
|
831
|
+
close: () => {
|
|
832
|
+
resolveStream?.()
|
|
833
|
+
},
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const store = createFakeStore()
|
|
839
|
+
const coordinator = new AgentCoordinator({
|
|
840
|
+
store: store as never,
|
|
841
|
+
onStateChange: () => {},
|
|
842
|
+
codexManager: fakeCodexManager as never,
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
await coordinator.send({
|
|
846
|
+
type: "chat.send",
|
|
847
|
+
chatId: "chat-1",
|
|
848
|
+
provider: "codex",
|
|
849
|
+
content: "run something with a background task",
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
// Wait for the result message to be persisted
|
|
853
|
+
await waitFor(() => store.messages.some((entry) => entry.kind === "result"))
|
|
854
|
+
|
|
855
|
+
// The active turn should be removed even though the stream is still open.
|
|
856
|
+
// This is the key assertion: the UI should show idle (not "Running...")
|
|
857
|
+
// so the user can send new messages without hitting stop.
|
|
858
|
+
expect(coordinator.getActiveStatuses().has("chat-1")).toBe(false)
|
|
859
|
+
expect(store.turnFinishedCount).toBe(1)
|
|
860
|
+
|
|
861
|
+
// The stream is still open, so it should be draining
|
|
862
|
+
expect(coordinator.getDrainingChatIds().has("chat-1")).toBe(true)
|
|
863
|
+
|
|
864
|
+
// Clean up the hanging stream
|
|
865
|
+
resolveStream()
|
|
866
|
+
|
|
867
|
+
// After the stream closes, draining should stop
|
|
868
|
+
await waitFor(() => !coordinator.getDrainingChatIds().has("chat-1"))
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
test("stopDraining closes the stream and removes from draining set", async () => {
|
|
872
|
+
let resolveStream!: () => void
|
|
873
|
+
let streamClosed = false
|
|
874
|
+
|
|
875
|
+
const fakeCodexManager = {
|
|
876
|
+
async startSession() {},
|
|
877
|
+
async startTurn(): Promise<HarnessTurn> {
|
|
878
|
+
async function* stream() {
|
|
879
|
+
yield {
|
|
880
|
+
type: "transcript" as const,
|
|
881
|
+
entry: timestamped({
|
|
882
|
+
kind: "system_init",
|
|
883
|
+
provider: "codex",
|
|
884
|
+
model: "gpt-5.4",
|
|
885
|
+
tools: [],
|
|
886
|
+
agents: [],
|
|
887
|
+
slashCommands: [],
|
|
888
|
+
mcpServers: [],
|
|
889
|
+
}),
|
|
890
|
+
}
|
|
891
|
+
yield {
|
|
892
|
+
type: "transcript" as const,
|
|
893
|
+
entry: timestamped({
|
|
894
|
+
kind: "result",
|
|
895
|
+
subtype: "success",
|
|
896
|
+
isError: false,
|
|
897
|
+
durationMs: 0,
|
|
898
|
+
result: "done",
|
|
899
|
+
}),
|
|
900
|
+
}
|
|
901
|
+
await new Promise<void>((resolve) => {
|
|
902
|
+
resolveStream = resolve
|
|
903
|
+
})
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return {
|
|
907
|
+
provider: "codex",
|
|
908
|
+
stream: stream(),
|
|
909
|
+
interrupt: async () => {},
|
|
910
|
+
close: () => {
|
|
911
|
+
streamClosed = true
|
|
912
|
+
resolveStream?.()
|
|
913
|
+
},
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const store = createFakeStore()
|
|
919
|
+
const coordinator = new AgentCoordinator({
|
|
920
|
+
store: store as never,
|
|
921
|
+
onStateChange: () => {},
|
|
922
|
+
codexManager: fakeCodexManager as never,
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
await coordinator.send({
|
|
926
|
+
type: "chat.send",
|
|
927
|
+
chatId: "chat-1",
|
|
928
|
+
provider: "codex",
|
|
929
|
+
content: "work",
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
await waitFor(() => coordinator.getDrainingChatIds().has("chat-1"))
|
|
933
|
+
|
|
934
|
+
await coordinator.stopDraining("chat-1")
|
|
935
|
+
|
|
936
|
+
expect(coordinator.getDrainingChatIds().has("chat-1")).toBe(false)
|
|
937
|
+
expect(streamClosed).toBe(true)
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
test("cancel immediately removes active turn so UI shows idle", async () => {
|
|
941
|
+
let resolveInterrupt!: () => void
|
|
942
|
+
const interruptCalled = new Promise<void>((resolve) => {
|
|
943
|
+
resolveInterrupt = resolve
|
|
944
|
+
})
|
|
945
|
+
// interrupt() that hangs until we resolve it — simulating a slow SDK
|
|
946
|
+
let interruptDone = false
|
|
947
|
+
|
|
948
|
+
const fakeCodexManager = {
|
|
949
|
+
async startSession() {},
|
|
950
|
+
async startTurn(): Promise<HarnessTurn> {
|
|
951
|
+
async function* stream() {
|
|
952
|
+
yield {
|
|
953
|
+
type: "transcript" as const,
|
|
954
|
+
entry: timestamped({
|
|
955
|
+
kind: "system_init",
|
|
956
|
+
provider: "codex",
|
|
957
|
+
model: "gpt-5.4",
|
|
958
|
+
tools: [],
|
|
959
|
+
agents: [],
|
|
960
|
+
slashCommands: [],
|
|
961
|
+
mcpServers: [],
|
|
962
|
+
}),
|
|
963
|
+
}
|
|
964
|
+
// Stream that never ends (simulates the SDK hanging)
|
|
965
|
+
await new Promise(() => {})
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return {
|
|
969
|
+
provider: "codex",
|
|
970
|
+
stream: stream(),
|
|
971
|
+
interrupt: async () => {
|
|
972
|
+
resolveInterrupt()
|
|
973
|
+
// Hang to simulate a slow interrupt
|
|
974
|
+
await new Promise<void>((resolve) => {
|
|
975
|
+
setTimeout(() => {
|
|
976
|
+
interruptDone = true
|
|
977
|
+
resolve()
|
|
978
|
+
}, 100)
|
|
979
|
+
})
|
|
980
|
+
},
|
|
981
|
+
close: () => {},
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const stateChanges: number[] = []
|
|
987
|
+
const store = createFakeStore()
|
|
988
|
+
const coordinator = new AgentCoordinator({
|
|
989
|
+
store: store as never,
|
|
990
|
+
onStateChange: () => {
|
|
991
|
+
stateChanges.push(Date.now())
|
|
992
|
+
},
|
|
993
|
+
codexManager: fakeCodexManager as never,
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
await coordinator.send({
|
|
997
|
+
type: "chat.send",
|
|
998
|
+
chatId: "chat-1",
|
|
999
|
+
provider: "codex",
|
|
1000
|
+
content: "do something",
|
|
1001
|
+
})
|
|
1002
|
+
|
|
1003
|
+
// Wait for the turn to be running
|
|
1004
|
+
await waitFor(() => coordinator.getActiveStatuses().get("chat-1") === "running")
|
|
1005
|
+
|
|
1006
|
+
// Cancel — this should immediately remove from active turns
|
|
1007
|
+
const cancelPromise = coordinator.cancel("chat-1")
|
|
1008
|
+
|
|
1009
|
+
// The turn should be removed from activeTurns immediately,
|
|
1010
|
+
// BEFORE interrupt() resolves
|
|
1011
|
+
await interruptCalled
|
|
1012
|
+
expect(coordinator.getActiveStatuses().has("chat-1")).toBe(false)
|
|
1013
|
+
expect(interruptDone).toBe(false) // interrupt is still in progress
|
|
1014
|
+
|
|
1015
|
+
await cancelPromise
|
|
1016
|
+
|
|
1017
|
+
// Verify only one "interrupted" message was appended
|
|
1018
|
+
const interruptedMessages = store.messages.filter((entry) => entry.kind === "interrupted")
|
|
1019
|
+
expect(interruptedMessages).toHaveLength(1)
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
test("concurrent cancel calls only produce a single interrupted message", async () => {
|
|
1023
|
+
let resolveStream!: () => void
|
|
1024
|
+
|
|
1025
|
+
const fakeCodexManager = {
|
|
1026
|
+
async startSession() {},
|
|
1027
|
+
async startTurn(): Promise<HarnessTurn> {
|
|
1028
|
+
async function* stream() {
|
|
1029
|
+
yield {
|
|
1030
|
+
type: "transcript" as const,
|
|
1031
|
+
entry: timestamped({
|
|
1032
|
+
kind: "system_init",
|
|
1033
|
+
provider: "codex",
|
|
1034
|
+
model: "gpt-5.4",
|
|
1035
|
+
tools: [],
|
|
1036
|
+
agents: [],
|
|
1037
|
+
slashCommands: [],
|
|
1038
|
+
mcpServers: [],
|
|
1039
|
+
}),
|
|
1040
|
+
}
|
|
1041
|
+
await new Promise<void>((resolve) => {
|
|
1042
|
+
resolveStream = resolve
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return {
|
|
1047
|
+
provider: "codex",
|
|
1048
|
+
stream: stream(),
|
|
1049
|
+
interrupt: async () => {
|
|
1050
|
+
resolveStream()
|
|
1051
|
+
},
|
|
1052
|
+
close: () => {},
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const store = createFakeStore()
|
|
1058
|
+
const coordinator = new AgentCoordinator({
|
|
1059
|
+
store: store as never,
|
|
1060
|
+
onStateChange: () => {},
|
|
1061
|
+
codexManager: fakeCodexManager as never,
|
|
1062
|
+
})
|
|
1063
|
+
|
|
1064
|
+
await coordinator.send({
|
|
1065
|
+
type: "chat.send",
|
|
1066
|
+
chatId: "chat-1",
|
|
1067
|
+
provider: "codex",
|
|
1068
|
+
content: "work",
|
|
1069
|
+
})
|
|
1070
|
+
|
|
1071
|
+
await waitFor(() => coordinator.getActiveStatuses().get("chat-1") === "running")
|
|
1072
|
+
|
|
1073
|
+
// Fire multiple cancel calls concurrently (simulating repeated stop button clicks)
|
|
1074
|
+
await Promise.all([
|
|
1075
|
+
coordinator.cancel("chat-1"),
|
|
1076
|
+
coordinator.cancel("chat-1"),
|
|
1077
|
+
coordinator.cancel("chat-1"),
|
|
1078
|
+
])
|
|
1079
|
+
|
|
1080
|
+
// Only one "interrupted" message should exist
|
|
1081
|
+
const interruptedMessages = store.messages.filter((entry) => entry.kind === "interrupted")
|
|
1082
|
+
expect(interruptedMessages).toHaveLength(1)
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
test("runTurn stops processing events after cancel", async () => {
|
|
1086
|
+
let resolveStream!: () => void
|
|
1087
|
+
|
|
1088
|
+
const fakeCodexManager = {
|
|
1089
|
+
async startSession() {},
|
|
1090
|
+
async startTurn(): Promise<HarnessTurn> {
|
|
1091
|
+
async function* stream() {
|
|
1092
|
+
yield {
|
|
1093
|
+
type: "transcript" as const,
|
|
1094
|
+
entry: timestamped({
|
|
1095
|
+
kind: "system_init",
|
|
1096
|
+
provider: "codex",
|
|
1097
|
+
model: "gpt-5.4",
|
|
1098
|
+
tools: [],
|
|
1099
|
+
agents: [],
|
|
1100
|
+
slashCommands: [],
|
|
1101
|
+
mcpServers: [],
|
|
1102
|
+
}),
|
|
1103
|
+
}
|
|
1104
|
+
// Wait for cancel, then yield another event that should be ignored
|
|
1105
|
+
await new Promise<void>((resolve) => {
|
|
1106
|
+
resolveStream = resolve
|
|
1107
|
+
})
|
|
1108
|
+
// This event arrives after cancel — should not be processed
|
|
1109
|
+
yield {
|
|
1110
|
+
type: "transcript" as const,
|
|
1111
|
+
entry: timestamped({
|
|
1112
|
+
kind: "assistant_text",
|
|
1113
|
+
text: "this should be ignored after cancel",
|
|
1114
|
+
}),
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return {
|
|
1119
|
+
provider: "codex",
|
|
1120
|
+
stream: stream(),
|
|
1121
|
+
interrupt: async () => {
|
|
1122
|
+
resolveStream()
|
|
1123
|
+
},
|
|
1124
|
+
close: () => {},
|
|
1125
|
+
}
|
|
1126
|
+
},
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const store = createFakeStore()
|
|
1130
|
+
const coordinator = new AgentCoordinator({
|
|
1131
|
+
store: store as never,
|
|
1132
|
+
onStateChange: () => {},
|
|
1133
|
+
codexManager: fakeCodexManager as never,
|
|
1134
|
+
})
|
|
1135
|
+
|
|
1136
|
+
await coordinator.send({
|
|
1137
|
+
type: "chat.send",
|
|
1138
|
+
chatId: "chat-1",
|
|
1139
|
+
provider: "codex",
|
|
1140
|
+
content: "work",
|
|
1141
|
+
})
|
|
1142
|
+
|
|
1143
|
+
await waitFor(() => coordinator.getActiveStatuses().get("chat-1") === "running")
|
|
1144
|
+
|
|
1145
|
+
const messageCountBefore = store.messages.filter((entry) => entry.kind === "assistant_text").length
|
|
1146
|
+
await coordinator.cancel("chat-1")
|
|
1147
|
+
|
|
1148
|
+
// Give the stream time to yield the extra event
|
|
1149
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
1150
|
+
|
|
1151
|
+
const postCancelTextMessages = store.messages.filter((entry) => entry.kind === "assistant_text")
|
|
1152
|
+
expect(postCancelTextMessages.length).toBe(messageCountBefore)
|
|
1153
|
+
})
|
|
1154
|
+
|
|
709
1155
|
test("cancelling a waiting codex exit-plan prompt discards it without starting a follow-up turn", async () => {
|
|
710
1156
|
let releaseInterrupt!: () => void
|
|
711
1157
|
const interrupted = new Promise<void>((resolve) => {
|
|
@@ -802,6 +1248,148 @@ describe("AgentCoordinator codex integration", () => {
|
|
|
802
1248
|
})
|
|
803
1249
|
})
|
|
804
1250
|
|
|
1251
|
+
describe("AgentCoordinator claude integration", () => {
|
|
1252
|
+
test("reuses a persistent Claude session across turns", async () => {
|
|
1253
|
+
const events = new AsyncEventQueue<any>()
|
|
1254
|
+
const startSessionCalls: Array<{ model: string; planMode: boolean; sessionToken: string | null }> = []
|
|
1255
|
+
const prompts: string[] = []
|
|
1256
|
+
|
|
1257
|
+
const store = createFakeStore()
|
|
1258
|
+
const coordinator = new AgentCoordinator({
|
|
1259
|
+
store: store as never,
|
|
1260
|
+
onStateChange: () => {},
|
|
1261
|
+
startClaudeSession: async (args) => {
|
|
1262
|
+
startSessionCalls.push({
|
|
1263
|
+
model: args.model,
|
|
1264
|
+
planMode: args.planMode,
|
|
1265
|
+
sessionToken: args.sessionToken,
|
|
1266
|
+
})
|
|
1267
|
+
|
|
1268
|
+
return {
|
|
1269
|
+
provider: "claude",
|
|
1270
|
+
stream: events,
|
|
1271
|
+
getAccountInfo: async () => null,
|
|
1272
|
+
interrupt: async () => {},
|
|
1273
|
+
close: () => {},
|
|
1274
|
+
setModel: async () => {},
|
|
1275
|
+
setPermissionMode: async () => {},
|
|
1276
|
+
sendPrompt: async (content: string) => {
|
|
1277
|
+
prompts.push(content)
|
|
1278
|
+
if (prompts.length === 1) {
|
|
1279
|
+
events.push({ type: "session_token" as const, sessionToken: "claude-session-1" })
|
|
1280
|
+
events.push({
|
|
1281
|
+
type: "transcript" as const,
|
|
1282
|
+
entry: timestamped({
|
|
1283
|
+
kind: "system_init",
|
|
1284
|
+
provider: "claude",
|
|
1285
|
+
model: "claude-opus-4-1",
|
|
1286
|
+
tools: [],
|
|
1287
|
+
agents: [],
|
|
1288
|
+
slashCommands: [],
|
|
1289
|
+
mcpServers: [],
|
|
1290
|
+
}),
|
|
1291
|
+
})
|
|
1292
|
+
}
|
|
1293
|
+
events.push({
|
|
1294
|
+
type: "transcript" as const,
|
|
1295
|
+
entry: timestamped({
|
|
1296
|
+
kind: "result",
|
|
1297
|
+
subtype: "success",
|
|
1298
|
+
isError: false,
|
|
1299
|
+
durationMs: 0,
|
|
1300
|
+
result: "done",
|
|
1301
|
+
}),
|
|
1302
|
+
})
|
|
1303
|
+
},
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
})
|
|
1307
|
+
|
|
1308
|
+
await coordinator.send({
|
|
1309
|
+
type: "chat.send",
|
|
1310
|
+
chatId: "chat-1",
|
|
1311
|
+
provider: "claude",
|
|
1312
|
+
content: "start background task",
|
|
1313
|
+
model: "claude-opus-4-1",
|
|
1314
|
+
})
|
|
1315
|
+
await waitFor(() => store.turnFinishedCount === 1)
|
|
1316
|
+
|
|
1317
|
+
await coordinator.send({
|
|
1318
|
+
type: "chat.send",
|
|
1319
|
+
chatId: "chat-1",
|
|
1320
|
+
provider: "claude",
|
|
1321
|
+
content: "check task output",
|
|
1322
|
+
model: "claude-opus-4-1",
|
|
1323
|
+
})
|
|
1324
|
+
await waitFor(() => store.turnFinishedCount === 2)
|
|
1325
|
+
|
|
1326
|
+
expect(startSessionCalls).toHaveLength(1)
|
|
1327
|
+
expect(startSessionCalls[0]?.planMode).toBe(false)
|
|
1328
|
+
expect(startSessionCalls[0]?.sessionToken).toBeNull()
|
|
1329
|
+
expect(prompts).toEqual(["start background task", "check task output"])
|
|
1330
|
+
expect(store.chat.sessionToken).toBe("claude-session-1")
|
|
1331
|
+
|
|
1332
|
+
events.close()
|
|
1333
|
+
})
|
|
1334
|
+
|
|
1335
|
+
test("Claude final results clear running state without using draining mode", async () => {
|
|
1336
|
+
const events = new AsyncEventQueue<any>()
|
|
1337
|
+
|
|
1338
|
+
const store = createFakeStore()
|
|
1339
|
+
const coordinator = new AgentCoordinator({
|
|
1340
|
+
store: store as never,
|
|
1341
|
+
onStateChange: () => {},
|
|
1342
|
+
startClaudeSession: async () => ({
|
|
1343
|
+
provider: "claude",
|
|
1344
|
+
stream: events,
|
|
1345
|
+
getAccountInfo: async () => null,
|
|
1346
|
+
interrupt: async () => {},
|
|
1347
|
+
close: () => {},
|
|
1348
|
+
setModel: async () => {},
|
|
1349
|
+
setPermissionMode: async () => {},
|
|
1350
|
+
sendPrompt: async () => {
|
|
1351
|
+
events.push({
|
|
1352
|
+
type: "transcript" as const,
|
|
1353
|
+
entry: timestamped({
|
|
1354
|
+
kind: "system_init",
|
|
1355
|
+
provider: "claude",
|
|
1356
|
+
model: "claude-opus-4-1",
|
|
1357
|
+
tools: [],
|
|
1358
|
+
agents: [],
|
|
1359
|
+
slashCommands: [],
|
|
1360
|
+
mcpServers: [],
|
|
1361
|
+
}),
|
|
1362
|
+
})
|
|
1363
|
+
events.push({
|
|
1364
|
+
type: "transcript" as const,
|
|
1365
|
+
entry: timestamped({
|
|
1366
|
+
kind: "result",
|
|
1367
|
+
subtype: "success",
|
|
1368
|
+
isError: false,
|
|
1369
|
+
durationMs: 0,
|
|
1370
|
+
result: "done",
|
|
1371
|
+
}),
|
|
1372
|
+
})
|
|
1373
|
+
},
|
|
1374
|
+
}),
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
await coordinator.send({
|
|
1378
|
+
type: "chat.send",
|
|
1379
|
+
chatId: "chat-1",
|
|
1380
|
+
provider: "claude",
|
|
1381
|
+
content: "run something",
|
|
1382
|
+
model: "claude-opus-4-1",
|
|
1383
|
+
})
|
|
1384
|
+
|
|
1385
|
+
await waitFor(() => store.turnFinishedCount === 1)
|
|
1386
|
+
expect(coordinator.getActiveStatuses().has("chat-1")).toBe(false)
|
|
1387
|
+
expect(coordinator.getDrainingChatIds().has("chat-1")).toBe(false)
|
|
1388
|
+
|
|
1389
|
+
events.close()
|
|
1390
|
+
})
|
|
1391
|
+
})
|
|
1392
|
+
|
|
805
1393
|
function createFakeStore() {
|
|
806
1394
|
const chat = {
|
|
807
1395
|
id: "chat-1",
|