@hienlh/ppm 0.17.16 → 0.17.18
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/CHANGELOG.md +19 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +5 -1
- package/dist/web/assets/{ai-resource-editor-CsossqDO.js → ai-resource-editor-BgWg-ty8.js} +1 -1
- package/dist/web/assets/{angular-html-CkxY2iBQ.js → angular-html-2g8YGiJY.js} +1 -1
- package/dist/web/assets/{angular-ts-BePeGkkC.js → angular-ts-BwSmoLfh.js} +1 -1
- package/dist/web/assets/{api-settings-DAk7D-NP.js → api-settings-BJTjIG4U.js} +1 -1
- package/dist/web/assets/{api-themes-84MT-XqR.js → api-themes-DuLpbQFV.js} +1 -1
- package/dist/web/assets/{apl-DBS7PWvB.js → apl-B44SMKHp.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-gLlhsjM9.js +1 -0
- package/dist/web/assets/{astro-Clbk1k4h.js → astro-E5620wek.js} +1 -1
- package/dist/web/assets/{audio-preview-DIkokvPM.js → audio-preview-CLn1LTxc.js} +1 -1
- package/dist/web/assets/{blade-BeBsBuGS.js → blade-D-VQWAHB.js} +1 -1
- package/dist/web/assets/{bsl-DYigT3JZ.js → bsl-Bq5qFzpU.js} +1 -1
- package/dist/web/assets/{cairo-BjpO78wI.js → cairo-Cn9-ZKRa.js} +1 -1
- package/dist/web/assets/chat-tab-tTUVo4G3.js +13 -0
- package/dist/web/assets/{cobol-CpuUx69s.js → cobol-IM3E3iCH.js} +1 -1
- package/dist/web/assets/code-editor-DMfUjdk5.js +10 -0
- package/dist/web/assets/{coffee-FoFd2Cxo.js → coffee-24wG7ekn.js} +1 -1
- package/dist/web/assets/{conflict-editor-Dqm1bkyL.js → conflict-editor-BosR4PW3.js} +3 -3
- package/dist/web/assets/{cpp-CO_nPfdr.js → cpp-DH9Cqt4g.js} +1 -1
- package/dist/web/assets/{crystal-D-UygAAF.js → crystal-4nZYHhdi.js} +1 -1
- package/dist/web/assets/{csv-preview-CvP4xzug.js → csv-preview-BqszurHp.js} +1 -1
- package/dist/web/assets/data-grid-overlay-editor-DxdOKRAY.js +1 -0
- package/dist/web/assets/data-grid-types-CzEQU9PJ.js +1 -0
- package/dist/web/assets/database-viewer-CiomoOU6.js +1 -0
- package/dist/web/assets/dialog-BpkAWOy8.js +1 -0
- package/dist/web/assets/{diff-viewer-BfWixe_z.js → diff-viewer-aOrOaGWx.js} +1 -1
- package/dist/web/assets/dist-Bn3E6I4R.js +11 -0
- package/dist/web/assets/{docx-preview-CguUoTKe.js → docx-preview-BAzpJdua.js} +1 -1
- package/dist/web/assets/{edge-BSzxzX11.js → edge-DTI9d2Qi.js} +1 -1
- package/dist/web/assets/{elixir-DH1-Mrep.js → elixir-Ys0wioho.js} +1 -1
- package/dist/web/assets/{elm-B0ln5J70.js → elm-Cihc9TAz.js} +1 -1
- package/dist/web/assets/{erb-GIz6NnlZ.js → erb-BwYBWK2-.js} +1 -1
- package/dist/web/assets/{erlang-B8MgYhib.js → erlang-CzlD_Joo.js} +1 -1
- package/dist/web/assets/esm-JLkfN0k-.js +2 -0
- package/dist/web/assets/{extension-webview-DTpFt1eL.js → extension-webview-CKUUvgOr.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-D_-TrK6U.js → fortran-fixed-form-CT90EtSp.js} +1 -1
- package/dist/web/assets/{fsharp-C9cLCk2b.js → fsharp-LasxezlU.js} +1 -1
- package/dist/web/assets/{gdresource-CsZ3lXwY.js → gdresource-XqgPdfiE.js} +1 -1
- package/dist/web/assets/{git-commit-xmq0KqrA.js → git-commit-CZBgPolw.js} +1 -1
- package/dist/web/assets/{git-log-panel-DUX7N5tN.js → git-log-panel-B6GFWhj4.js} +1 -1
- package/dist/web/assets/{git-rebase-D3O-jsSX.js → git-rebase-C2VzKDB1.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BKGq--Jw.js +1 -0
- package/dist/web/assets/glide-data-grid-z9FPvRBG.js +138 -0
- package/dist/web/assets/{glimmer-js-g74gESrC.js → glimmer-js-DfjWcOLD.js} +1 -1
- package/dist/web/assets/{glimmer-ts-DF4aDWHT.js → glimmer-ts-DBldQATc.js} +1 -1
- package/dist/web/assets/{glsl-D5LmfW7l.js → glsl-CzNv1NPP.js} +1 -1
- package/dist/web/assets/{graphql-_KI4RuXL.js → graphql-DDyLtQb4.js} +1 -1
- package/dist/web/assets/group-chat-tab-Bu31mwmF.js +1 -0
- package/dist/web/assets/{hack-B1NaTC39.js → hack-CtoYNYkC.js} +1 -1
- package/dist/web/assets/{haml-DDFXadZ4.js → haml-CnBVap7G.js} +1 -1
- package/dist/web/assets/{handlebars-Bp-Jq3K9.js → handlebars-Dg51esUH.js} +1 -1
- package/dist/web/assets/{html-DyB9_5Tj.js → html-Ec2_evSy.js} +1 -1
- package/dist/web/assets/{html-derivative-CC60wi-3.js → html-derivative-Cy-F7ZqL.js} +1 -1
- package/dist/web/assets/{http-C-9qngH4.js → http-C236v8Tg.js} +1 -1
- package/dist/web/assets/{hurl-BtxgpNDs.js → hurl-0rMD2gPU.js} +1 -1
- package/dist/web/assets/{hxml-DOoOS9op.js → hxml-e5uS5jTD.js} +1 -1
- package/dist/web/assets/{image-preview-CZg6FQ3w.js → image-preview-CaX4gpN6.js} +1 -1
- package/dist/web/assets/index-4M24PSHW.js +50 -0
- package/dist/web/assets/index-BYFXiFsa.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BOhGriBF.js +1 -0
- package/dist/web/assets/{jinja-cN-150ao.js → jinja-DybFmhfq.js} +1 -1
- package/dist/web/assets/{jison-CqjsqPq5.js → jison-hkHbixdZ.js} +1 -1
- package/dist/web/assets/{julia-C-I5m8W4.js → julia-QnxY6sRE.js} +1 -1
- package/dist/web/assets/{just-CgsSW9zs.js → just-1Rs-nm8g.js} +1 -1
- package/dist/web/assets/keybindings-store-Cv-U85V6.js +1 -0
- package/dist/web/assets/{latex-BFzR9N2d.js → latex-jEBQqA7u.js} +1 -1
- package/dist/web/assets/{liquid-BWfr-_iC.js → liquid-DfJaZpxG.js} +1 -1
- package/dist/web/assets/{lua-DsXVun6g.js → lua-ti0Jr6-C.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CiQkvWP_.js → markdown-renderer-BNXJygz9.js} +3 -3
- package/dist/web/assets/markdown-renderer-DCsEEdo7.js +1 -0
- package/dist/web/assets/{marko-DWBbo09n.js → marko-DNj4AhPg.js} +1 -1
- package/dist/web/assets/{mdc-CUMe9LOG.js → mdc-DPFwQFe4.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet--5_YEewV.js +1 -0
- package/dist/web/assets/{nextflow-CoYbADOm.js → nextflow-BUrFV5aB.js} +1 -1
- package/dist/web/assets/{nginx-z-JRZREw.js → nginx-BrW9NdOb.js} +1 -1
- package/dist/web/assets/{nim-Ci4LHyBm.js → nim-DS_6PF2p.js} +1 -1
- package/dist/web/assets/notification-store-DADt1gJH.js +1 -0
- package/dist/web/assets/{number-overlay-editor-lnnuUpPI.js → number-overlay-editor-CReXWiQ1.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-CdLFnL8a.js +1 -0
- package/dist/web/assets/{panel-store-D6RV_ztx.js → panel-store-DnbT1c8L.js} +1 -1
- package/dist/web/assets/{pdf-preview-B6ZqANpw.js → pdf-preview-DZzbm9A4.js} +1 -1
- package/dist/web/assets/{perl-pbDimKOO.js → perl-ytm9vKO_.js} +1 -1
- package/dist/web/assets/{php-B003E5v7.js → php-DWTD1Usw.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-COC-h0zz.js +1 -0
- package/dist/web/assets/{trash-2-CbbD5QF1.js → plus-DcuJuDC3.js} +1 -1
- package/dist/web/assets/postgres-viewer-BxpeiVkt.js +3 -0
- package/dist/web/assets/{project-store-Cekcjcom.js → project-store-Sbnqc41N.js} +1 -1
- package/dist/web/assets/{pug-AoueAWJo.js → pug-Bt7KL3_r.js} +1 -1
- package/dist/web/assets/{qml-CepxioQA.js → qml-Dexdon5J.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-CiCfdJNL.js +1 -0
- package/dist/web/assets/{razor-lIvU-Gu-.js → razor-DnYCSl0G.js} +1 -1
- package/dist/web/assets/{rst-Dn-dwElE.js → rst-BEFYzwF8.js} +1 -1
- package/dist/web/assets/{ruby-DmsD1Iz5.js → ruby-BPaXFEjN.js} +1 -1
- package/dist/web/assets/{sas-6IM2P4YP.js → sas-CywiNBp8.js} +1 -1
- package/dist/web/assets/{scss-RgYEZQqh.js → scss-CGk0L6hN.js} +1 -1
- package/dist/web/assets/{settings-store-BkSf4SPQ.js → settings-store-DkRZhj-g.js} +2 -2
- package/dist/web/assets/settings-tab-DJOhpW09.js +1 -0
- package/dist/web/assets/{shaderlab-B0MNEwt4.js → shaderlab-Byjk0kH7.js} +1 -1
- package/dist/web/assets/{shellsession-F8S4LrQI.js → shellsession-DbA70QB6.js} +1 -1
- package/dist/web/assets/{soy-DDlMbX7u.js → soy-Bm97kWiA.js} +1 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +1 -0
- package/dist/web/assets/{sparql-kthmGAAe.js → sparql-CawLnniT.js} +1 -1
- package/dist/web/assets/{sql-query-editor-BYNM0RRl.js → sql-query-editor-9TwIPvRj.js} +1 -1
- package/dist/web/assets/sqlite-viewer-D-3i_x28.js +1 -0
- package/dist/web/assets/{stata-Tfbew8La.js → stata-RifEDtNO.js} +1 -1
- package/dist/web/assets/{surrealql-C0LIGqRx.js → surrealql-DGRQa5Lm.js} +1 -1
- package/dist/web/assets/{svelte-CfTy8CGF.js → svelte-w9Hb12aM.js} +1 -1
- package/dist/web/assets/system-monitor-tab-CuVVp2wt.js +1 -0
- package/dist/web/assets/{tab-store-CcLQfu8H.js → tab-store-vn0viMOP.js} +1 -1
- package/dist/web/assets/{templ-V5H1Sbfy.js → templ-BpUwjJXV.js} +1 -1
- package/dist/web/assets/{terminal-tab-CK3lX-0Y.js → terminal-tab-DrSqSrdX.js} +2 -2
- package/dist/web/assets/{tex-C3H3uMZf.js → tex-DJFSsuTD.js} +1 -1
- package/dist/web/assets/tool-cards-CtE_JnF-.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-CZwuSCtA.js +1 -0
- package/dist/web/assets/{ts-tags-7AXgG5YS.js → ts-tags-vp9Qvx2O.js} +1 -1
- package/dist/web/assets/{twig-BSyS38T3.js → twig-CoazxjRB.js} +1 -1
- package/dist/web/assets/{use-blob-url-CpyRr7Cx.js → use-blob-url-DdXgVqfz.js} +1 -1
- package/dist/web/assets/use-is-mobile-CE6AamG6.js +1 -0
- package/dist/web/assets/{use-monaco-theme-Jj2OxkAE.js → use-monaco-theme-BvzqffqR.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-DGOXKrJC.js → vendor-mermaid-DPpYkrMf.js} +193 -193
- package/dist/web/assets/{video-preview-BOHxkwKG.js → video-preview-DxdyVATR.js} +1 -1
- package/dist/web/assets/{vue-C4d_br9u.js → vue-CnshsiXd.js} +1 -1
- package/dist/web/assets/{vue-html-CGdoz4vW.js → vue-html-CZshynFp.js} +1 -1
- package/dist/web/assets/{vue-vine-CqNolQlN.js → vue-vine-C5gtp_T1.js} +1 -1
- package/dist/web/assets/{xml-UmCmu-Od.js → xml-BEGJasgj.js} +1 -1
- package/dist/web/assets/{xsl-DJG6USGh.js → xsl-DXpRLjYk.js} +1 -1
- package/dist/web/index.html +22 -20
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/journal/binary-self-upgrade-260724.md +66 -0
- package/nul/docs/claude-agent-sdk-reference.md +780 -0
- package/nul/docs/code-standards.md +1080 -0
- package/nul/docs/codebase-summary.md +916 -0
- package/nul/docs/deployment-guide.md +702 -0
- package/nul/docs/design-guidelines.md +767 -0
- package/nul/docs/extension-development-guide.md +629 -0
- package/nul/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
- package/nul/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
- package/nul/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
- package/nul/docs/journals/260414-2001-bundled-extensions.md +219 -0
- package/nul/docs/journals/260415-frontend-memory-optimization.md +73 -0
- package/nul/docs/lessons-learned.md +70 -0
- package/nul/docs/project-changelog.md +574 -0
- package/nul/docs/project-overview-pdr.md +205 -0
- package/nul/docs/project-roadmap.md +222 -0
- package/nul/docs/system-architecture.md +1961 -0
- package/package.json +1 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/spike-group-channel.mjs +156 -0
- package/spike-group-chat.mjs +181 -0
- package/src/server/routes/group-chat.ts +118 -9
- package/src/services/group-chat/agent-runner.ts +15 -2
- package/src/services/group-chat/context-window.ts +15 -0
- package/src/services/group-chat/group-chat.service.ts +178 -79
- package/src/services/group-chat/group-chat.store.ts +30 -0
- package/src/services/group-chat/responder-router.ts +110 -0
- package/src/services/group-chat/transcript-archive.ts +18 -0
- package/src/services/group-chat/turn-engine.ts +183 -105
- package/src/services/jsonl-transcript-parser.ts +36 -0
- package/src/types/group-chat-ws.ts +2 -2
- package/src/types/group-chat.ts +33 -4
- package/src/web/components/group-chat/group-chat-tab.tsx +109 -14
- package/src/web/components/group-chat/group-create-dialog.tsx +10 -3
- package/src/web/components/group-chat/group-full-transcript-view.tsx +9 -8
- package/src/web/components/group-chat/group-member-edit-dialog.tsx +108 -0
- package/src/web/components/group-chat/group-member-roster.tsx +51 -23
- package/src/web/components/group-chat/group-message-item.tsx +45 -26
- package/src/web/components/group-chat/group-transcript-messages.tsx +126 -0
- package/src/web/components/layout/cloud-share-popover.tsx +10 -2
- package/src/web/components/layout/mobile-drawer-tab-bar.tsx +10 -0
- package/src/web/components/layout/mobile-drawer.tsx +26 -9
- package/src/web/components/layout/nav-section-rail.tsx +8 -2
- package/src/web/components/layout/upgrade-button.tsx +33 -11
- package/src/web/hooks/use-group-chat.ts +8 -1
- package/src/web/lib/api-group-chat.ts +47 -4
- package/src/web/lib/sidebar-tabs/tab-registry.ts +3 -1
- package/dist/web/assets/architecture-PBZL5I3N-NAS99xy0.js +0 -1
- package/dist/web/assets/chat-tab-Bh6l253v.js +0 -18
- package/dist/web/assets/click-outside-container-DXZVmhaX.js +0 -1
- package/dist/web/assets/code-editor-B7F-d4DK.js +0 -10
- package/dist/web/assets/data-grid-overlay-editor-CUlFsD-D.js +0 -1
- package/dist/web/assets/database-viewer-5oNTPgjF.js +0 -1
- package/dist/web/assets/dist-BsziT-tR.js +0 -11
- package/dist/web/assets/esm-CjVeOTNQ.js +0 -2
- package/dist/web/assets/gitGraph-HDMCJU4V-B4I3L4Sf.js +0 -1
- package/dist/web/assets/glide-data-grid-7vB1SNne.js +0 -138
- package/dist/web/assets/group-chat-tab-CNECY-zg.js +0 -1
- package/dist/web/assets/index-D2G6kmTs.js +0 -50
- package/dist/web/assets/index-DCmrZnq0.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-CBbbOe3C.js +0 -1
- package/dist/web/assets/input-24Nf_edC.js +0 -1
- package/dist/web/assets/keybindings-store-D8Cppv4w.js +0 -1
- package/dist/web/assets/notification-store-DOGxkWih.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-fr8-QSTF.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-Biu7_Pbb.js +0 -1
- package/dist/web/assets/postgres-viewer-B23xTCSs.js +0 -3
- package/dist/web/assets/radar-KQ55EAFF-CMtNRpK6.js +0 -1
- package/dist/web/assets/settings-tab-D5hAA1bY.js +0 -1
- package/dist/web/assets/sparkles-2ii4lR7b.js +0 -1
- package/dist/web/assets/sqlite-viewer-DW9f2Hqj.js +0 -1
- package/dist/web/assets/system-monitor-tab-B5f4pgdX.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-ZYVFjoaN.js +0 -1
- package/dist/web/assets/use-websocket-DrDBgIWU.js +0 -1
- /package/dist/web/assets/{abap-BZ3J5hF3.js → abap-wT6fgqwv.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CkyGfT-8.js → actionscript-3-CqcHpPYj.js} +0 -0
- /package/dist/web/assets/{ada-CxNLBoe7.js → ada-DWE3Hn4E.js} +0 -0
- /package/dist/web/assets/{andromeeda-BPN5rsDz.js → andromeeda-DpQ-y-Jt.js} +0 -0
- /package/dist/web/assets/{apache-C1F5wHL6.js → apache-LRTUP4r3.js} +0 -0
- /package/dist/web/assets/{apex-BaNixdO-.js → apex-DwVeH5_h.js} +0 -0
- /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-BK4NPNoY.js} +0 -0
- /package/dist/web/assets/{applescript-Cl_pdcch.js → applescript-DyQiO9JU.js} +0 -0
- /package/dist/web/assets/{apply-theme-ORrEdHKT.js → apply-theme-DxKsrDLA.js} +0 -0
- /package/dist/web/assets/{ara-B6UAE7M2.js → ara-Cf-OWd2S.js} +0 -0
- /package/dist/web/assets/{asciidoc-D9c9Twki.js → asciidoc-gocbKcVK.js} +0 -0
- /package/dist/web/assets/{asm-CNmtv7Y9.js → asm-C3meb5HY.js} +0 -0
- /package/dist/web/assets/{aurora-x-C9z-NyzR.js → aurora-x-BYcXaCIg.js} +0 -0
- /package/dist/web/assets/{awk-BfLHEnKa.js → awk-BcT6nIdN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-Bn7trkt_.js → ayu-dark-CLZPoccg.js} +0 -0
- /package/dist/web/assets/{ayu-light-Dpya9hPf.js → ayu-light-CTyRQdS2.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cc0jfABD.js → ayu-mirage-DjqfqZdo.js} +0 -0
- /package/dist/web/assets/{ballerina-BtiCmN7T.js → ballerina-BCARR-zK.js} +0 -0
- /package/dist/web/assets/{bat-tmFM76zR.js → bat-_yaRjWOb.js} +0 -0
- /package/dist/web/assets/{beancount-BjPqRrcH.js → beancount-BPESBMS9.js} +0 -0
- /package/dist/web/assets/{berry-D0SQBxN2.js → berry-BSLE-Sf8.js} +0 -0
- /package/dist/web/assets/{bibtex-CkjRWQKr.js → bibtex-CvUWWCVx.js} +0 -0
- /package/dist/web/assets/{bicep-Bhq28Cxc.js → bicep--vcqqALZ.js} +0 -0
- /package/dist/web/assets/{bird2-DFGInYb3.js → bird2-Clclqgh6.js} +0 -0
- /package/dist/web/assets/{c-CP8cB4k_.js → c-CSC2LwTp.js} +0 -0
- /package/dist/web/assets/{c3-oRytyeWo.js → c3-Cl1_jUnm.js} +0 -0
- /package/dist/web/assets/{cadence-Inf1rGb7.js → cadence-BwAD7vSC.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DAzribQo.js → catppuccin-frappe-hr0HKoFX.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-B6Y_mCCQ.js → catppuccin-latte-DXEIYlFO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-COLbYqVL.js → catppuccin-macchiato-B2zST7sA.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-DKWaML1B.js → catppuccin-mocha-CvrdnYv9.js} +0 -0
- /package/dist/web/assets/{chevron-right-BBc6ATtm.js → chevron-right-DeUue2aK.js} +0 -0
- /package/dist/web/assets/{circle-question-mark-CveWaCux.js → circle-question-mark-PaJgx-3d.js} +0 -0
- /package/dist/web/assets/{clarity-_ZLLrrsX.js → clarity-BeQd5TQD.js} +0 -0
- /package/dist/web/assets/{clojure-DWGVCg_P.js → clojure-Csdo0vb_.js} +0 -0
- /package/dist/web/assets/{cmake-Cwh3mug7.js → cmake-Cf0q7ddg.js} +0 -0
- /package/dist/web/assets/{code-7ZAlTvsm.js → code-TekEnDFM.js} +0 -0
- /package/dist/web/assets/{codeowners-BdV6CFYi.js → codeowners-CUEzlC7R.js} +0 -0
- /package/dist/web/assets/{codeql-NvupDIDf.js → codeql-DxHyogQt.js} +0 -0
- /package/dist/web/assets/{common-lisp-B-vtBh5k.js → common-lisp-WLb-tAda.js} +0 -0
- /package/dist/web/assets/{coq-M8-FTfiZ.js → coq-DFtMD6Wn.js} +0 -0
- /package/dist/web/assets/{csharp-B7Ugdtd-.js → csharp-DEnvXVjs.js} +0 -0
- /package/dist/web/assets/{css-86k3Z-YA.js → css-DMPpCfkZ.js} +0 -0
- /package/dist/web/assets/{csv-BX5XYhfH.js → csv-DTSgNSaP.js} +0 -0
- /package/dist/web/assets/{csv-parser-d56Fzpwc.js → csv-parser-B_xNn07y.js} +0 -0
- /package/dist/web/assets/{cue-B7099gQa.js → cue-BEmVIZmd.js} +0 -0
- /package/dist/web/assets/{cypher-D99mWVIu.js → cypher-CncQVhvU.js} +0 -0
- /package/dist/web/assets/{d-CFE8oQwh.js → d-BzUSBVex.js} +0 -0
- /package/dist/web/assets/{dark-plus-BEeeDwdg.js → dark-plus-mysFNRq2.js} +0 -0
- /package/dist/web/assets/{dart-HVSWGnI5.js → dart-BBu3nkcE.js} +0 -0
- /package/dist/web/assets/{database-BLht7BVM.js → database-COYAMZaw.js} +0 -0
- /package/dist/web/assets/{dax-DjDgSJkh.js → dax-DcLVz9_9.js} +0 -0
- /package/dist/web/assets/{desktop-B5A3i7J1.js → desktop-DS2PdvoU.js} +0 -0
- /package/dist/web/assets/{diff-CpTrlaE9.js → diff-aUYk5Qys.js} +0 -0
- /package/dist/web/assets/{dist-BSCgJs-V.js → dist-Dxl3dqhC.js} +0 -0
- /package/dist/web/assets/{docker-xBhmJeHj.js → docker-12Lj1QUD.js} +0 -0
- /package/dist/web/assets/{dotenv-DKKhLEv5.js → dotenv-D-P4bzvl.js} +0 -0
- /package/dist/web/assets/{dracula-CVwQuzra.js → dracula-C-eRNn4o.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CG6mB-lf.js → dracula-soft-CBB1BI7O.js} +0 -0
- /package/dist/web/assets/{dream-maker-SC-IdYY3.js → dream-maker-C_j5A0r7.js} +0 -0
- /package/dist/web/assets/{edit-diff-preview-C50i1H63.js → edit-diff-preview-CRMu-6r1.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CIBFq6W8.js → emacs-lisp-W54LT7rh.js} +0 -0
- /package/dist/web/assets/{everforest-dark-odRT_lec.js → everforest-dark-B_ISUDeh.js} +0 -0
- /package/dist/web/assets/{everforest-light-or1Voqjl.js → everforest-light-CNUYa354.js} +0 -0
- /package/dist/web/assets/{fennel-tQd1z3iu.js → fennel-DK82-oso.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-C-lL0g_E.js → file-exclamation-point-DBEC-Kst.js} +0 -0
- /package/dist/web/assets/{fish-DSuZQG0k.js → fish-CbJVJzQj.js} +0 -0
- /package/dist/web/assets/{fluent-D7xI8a8e.js → fluent-Cug5cOL1.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BlAtcPd3.js → fortran-free-form-amHz32Zw.js} +0 -0
- /package/dist/web/assets/{gdscript-VsEQXBPm.js → gdscript-t_G6VYpv.js} +0 -0
- /package/dist/web/assets/{gdshader-iqHMOdwW.js → gdshader-DqZaHh4E.js} +0 -0
- /package/dist/web/assets/{genie-C4XU4g1a.js → genie-CIJtFlZK.js} +0 -0
- /package/dist/web/assets/{gherkin-C4nfQzAB.js → gherkin-CalidqLY.js} +0 -0
- /package/dist/web/assets/{github-dark-B_pbG3CC.js → github-dark-BG2xX0wa.js} +0 -0
- /package/dist/web/assets/{github-dark-default-kFbx7C95.js → github-dark-default-tDp_EOBs.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CpVfQEqs.js → github-dark-dimmed-DlSfPwW2.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-DYKqQwZh.js → github-dark-high-contrast-qZM7j5KB.js} +0 -0
- /package/dist/web/assets/{github-light-gEYBmKaj.js → github-light-CnvWTiyb.js} +0 -0
- /package/dist/web/assets/{github-light-default-DVHfMPQ2.js → github-light-default-Dy6OAt2Q.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-ClVJDu-q.js → github-light-high-contrast-1BHQCOPl.js} +0 -0
- /package/dist/web/assets/{gleam-BLaPu7ft.js → gleam-D-2cpXVX.js} +0 -0
- /package/dist/web/assets/{gn-BKgerUyI.js → gn-C1cC4xLc.js} +0 -0
- /package/dist/web/assets/{gnuplot-cqJuEFb-.js → gnuplot-2Gp459mV.js} +0 -0
- /package/dist/web/assets/{go-B2EHdyD8.js → go-BA1q0q8h.js} +0 -0
- /package/dist/web/assets/{groovy-DdImoj8s.js → groovy-BlIa-VfI.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DlwstAds.js → gruvbox-dark-hard-BMIwVwaJ.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-CiYEd1vd.js → gruvbox-dark-medium-DxfuBW3n.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-C-wEa4h4.js → gruvbox-dark-soft-_NSslDDi.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-BqYmfna-.js → gruvbox-light-hard-CP5MIsL9.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-CxmfalT2.js → gruvbox-light-medium-BTMursH0.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DGWXXMCB.js → gruvbox-light-soft-Bv6zFoVX.js} +0 -0
- /package/dist/web/assets/{haskell-H_C_IuVb.js → haskell-Da8UeSv-.js} +0 -0
- /package/dist/web/assets/{haxe-CGNSMpeZ.js → haxe-mcKlcxBa.js} +0 -0
- /package/dist/web/assets/{hcl-B7bSTSQf.js → hcl-BdZcx5JX.js} +0 -0
- /package/dist/web/assets/{hjson-DlJ-glQZ.js → hjson-pFXYbN4t.js} +0 -0
- /package/dist/web/assets/{hlsl-CehW5SJN.js → hlsl-Z0II0KR8.js} +0 -0
- /package/dist/web/assets/{horizon-Beabcz2d.js → horizon-CufN8DyP.js} +0 -0
- /package/dist/web/assets/{horizon-bright-BC87RXPa.js → horizon-bright-5qoQVyIY.js} +0 -0
- /package/dist/web/assets/{houston-Da7RFwUy.js → houston-DOFkQwep.js} +0 -0
- /package/dist/web/assets/{hy-m1H70w19.js → hy-BVbDMDcG.js} +0 -0
- /package/dist/web/assets/{imba-MExFsdAA.js → imba-DRM6LLS5.js} +0 -0
- /package/dist/web/assets/{ini-CJy_zmwy.js → ini-2KCnNvmz.js} +0 -0
- /package/dist/web/assets/{java-DqDFvJBH.js → java-BmvH-Ou1.js} +0 -0
- /package/dist/web/assets/{javascript-C3Wyv22X.js → javascript-B7VhU1Yc.js} +0 -0
- /package/dist/web/assets/{json-DnGBw_DZ.js → json-X1pNk0n7.js} +0 -0
- /package/dist/web/assets/{json5-CRN-zoN4.js → json5-CJeEJPTg.js} +0 -0
- /package/dist/web/assets/{jsonc-CKRUZNRo.js → jsonc-C1haTBsf.js} +0 -0
- /package/dist/web/assets/{jsonl-CGfD6FrH.js → jsonl-wQ8-AJgs.js} +0 -0
- /package/dist/web/assets/{jsonnet-nbFuDj0o.js → jsonnet-BrEjY3Xh.js} +0 -0
- /package/dist/web/assets/{jssm-D0ShzIqD.js → jssm-C6LgYbOs.js} +0 -0
- /package/dist/web/assets/{jsx-D27hbRbs.js → jsx-B2cfD_Fb.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-Ov3f8K0T.js → kanagawa-dragon-6UCcJpLw.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLyOqh-g.js → kanagawa-lotus-DOmfpsRg.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-Kj3dzabH.js → kanagawa-wave-CpxmPE7N.js} +0 -0
- /package/dist/web/assets/{kdl-DFF58Gu4.js → kdl-BrLEivmb.js} +0 -0
- /package/dist/web/assets/{kotlin-BM4PKaB4.js → kotlin-BO2FUnDB.js} +0 -0
- /package/dist/web/assets/{kusto-DrvHLCv5.js → kusto-Cev1yiq9.js} +0 -0
- /package/dist/web/assets/{laserwave-Db6i6ZkO.js → laserwave-BlEkf-eH.js} +0 -0
- /package/dist/web/assets/{lean-D0hEjzwk.js → lean-BLS3Xk2w.js} +0 -0
- /package/dist/web/assets/{less-BEj4npGG.js → less-BXmcsqcq.js} +0 -0
- /package/dist/web/assets/{lib-CBdlQ75W.js → lib-CxSBdrd7.js} +0 -0
- /package/dist/web/assets/{light-plus-DLIIbDea.js → light-plus-DS6zriYl.js} +0 -0
- /package/dist/web/assets/{llvm-DMt6umsO.js → llvm-C_wLnJgX.js} +0 -0
- /package/dist/web/assets/{log-BopvdtPW.js → log-Cg8pvmWi.js} +0 -0
- /package/dist/web/assets/{logo-CYSUFPFR.js → logo-C9-kEvPW.js} +0 -0
- /package/dist/web/assets/{luau-XC611HNO.js → luau-NwaGugfZ.js} +0 -0
- /package/dist/web/assets/{make-BMYZIfBj.js → make-DBpJE-Z8.js} +0 -0
- /package/dist/web/assets/{markdown-BLza5nvF.js → markdown-B7ZTAu7B.js} +0 -0
- /package/dist/web/assets/{material-theme-Uw27UfJW.js → material-theme-BsXH-Mss.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-dfqt8XBY.js → material-theme-darker-CW9OlIeY.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CPtBTblV.js → material-theme-lighter-D2PAh0Kw.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-BfZgVqC-.js → material-theme-ocean-Vz6DBVi3.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DAZoVYg8.js → material-theme-palenight-D-GleP3G.js} +0 -0
- /package/dist/web/assets/{matlab-cSzmquiW.js → matlab-C7MYlMSn.js} +0 -0
- /package/dist/web/assets/{mdx-Dq0kW5VM.js → mdx-FkOOX3Dq.js} +0 -0
- /package/dist/web/assets/{mermaid-DoIKF0yS.js → mermaid-C_9qw5it.js} +0 -0
- /package/dist/web/assets/{min-dark-DyraimDU.js → min-dark-BowNvxk2.js} +0 -0
- /package/dist/web/assets/{min-light-Co9cbcUv.js → min-light-x8oWUmbL.js} +0 -0
- /package/dist/web/assets/{mipsasm-ph6cKgHn.js → mipsasm-Dmi1AZOT.js} +0 -0
- /package/dist/web/assets/{mojo-BBT0XwmY.js → mojo-C6z7mry2.js} +0 -0
- /package/dist/web/assets/{monokai-IP0T5Rtg.js → monokai-BQ4e_Hn0.js} +0 -0
- /package/dist/web/assets/{moonbit-B8cogNXl.js → moonbit-CEeNIoB4.js} +0 -0
- /package/dist/web/assets/{move-DkY26NtM.js → move-bALfKUa7.js} +0 -0
- /package/dist/web/assets/{narrat-rBs3byG0.js → narrat-DX4JlAWT.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-DkO9wMLP.js → nextflow-groovy-CKQWtRkO.js} +0 -0
- /package/dist/web/assets/{night-owl-jVR4lFji.js → night-owl-D4PQd726.js} +0 -0
- /package/dist/web/assets/{night-owl-light-fBYJ7s3U.js → night-owl-light-BQo_YHll.js} +0 -0
- /package/dist/web/assets/{nix-WX9oavu6.js → nix-B1jThkGj.js} +0 -0
- /package/dist/web/assets/{nord-Cf7G21Pu.js → nord-35quHUKo.js} +0 -0
- /package/dist/web/assets/{nushell-DQdzcJ1H.js → nushell-L6DbJTuL.js} +0 -0
- /package/dist/web/assets/{objective-c-JSO2uvZ-.js → objective-c-CTZ4bR2U.js} +0 -0
- /package/dist/web/assets/{objective-cpp-CZMGJ9Fb.js → objective-cpp-Dm1YDIdA.js} +0 -0
- /package/dist/web/assets/{ocaml-C4S4NSDo.js → ocaml-B3n__LGy.js} +0 -0
- /package/dist/web/assets/{odin-Y2jXCgVq.js → odin-DmqZ8n3i.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-dVIxuoJY.js → one-dark-pro-16_UaUig.js} +0 -0
- /package/dist/web/assets/{one-light-BQ1IKfD1.js → one-light-B-GGjWn5.js} +0 -0
- /package/dist/web/assets/{openscad-Bddw4I3z.js → openscad-DTFB9Sj6.js} +0 -0
- /package/dist/web/assets/{pascal-BKVXrzC1.js → pascal-RcT1dQYN.js} +0 -0
- /package/dist/web/assets/{pkl-BTd-YRsO.js → pkl-Dr4yLQ_o.js} +0 -0
- /package/dist/web/assets/{plastic-eiGvBIxV.js → plastic-hp2Nry0R.js} +0 -0
- /package/dist/web/assets/{plsql-C290tieP.js → plsql-pFcO4WZ0.js} +0 -0
- /package/dist/web/assets/{po-DyDwh0tx.js → po-BkZ0_HvY.js} +0 -0
- /package/dist/web/assets/{poimandres-B87zc_g-.js → poimandres-BcTrxj7f.js} +0 -0
- /package/dist/web/assets/{polar-Cdub1YMT.js → polar-DHK3q8FF.js} +0 -0
- /package/dist/web/assets/{postcss-BcI_3K0v.js → postcss-Bv7WzijA.js} +0 -0
- /package/dist/web/assets/{powerquery-OdMrkJSe.js → powerquery-DpaU_hW9.js} +0 -0
- /package/dist/web/assets/{powershell-C8auT6xg.js → powershell-_Rqc0MGP.js} +0 -0
- /package/dist/web/assets/{prisma-DIbVv8PA.js → prisma-wyegE8us.js} +0 -0
- /package/dist/web/assets/{prolog-DbTOH3Ld.js → prolog-DspHCURv.js} +0 -0
- /package/dist/web/assets/{proto-C4Cq2dCD.js → proto-BOBiNGf5.js} +0 -0
- /package/dist/web/assets/{puppet-DJp3uqum.js → puppet-DfLvV8h2.js} +0 -0
- /package/dist/web/assets/{purescript-CLsV4rPU.js → purescript-BY5ZSOZy.js} +0 -0
- /package/dist/web/assets/{python-2gBtEza2.js → python-B758mMeg.js} +0 -0
- /package/dist/web/assets/{qmldir-DfhULcua.js → qmldir-C75bpHvy.js} +0 -0
- /package/dist/web/assets/{qss-DK--VTTV.js → qss-DcLTlbLd.js} +0 -0
- /package/dist/web/assets/{r-BArBO2Sa.js → r-D0G_Y1P5.js} +0 -0
- /package/dist/web/assets/{racket-5mijHsVy.js → racket-DTye_5qn.js} +0 -0
- /package/dist/web/assets/{raku-ir0suElk.js → raku-uJXBXkV0.js} +0 -0
- /package/dist/web/assets/{react-J9g6e3Rq.js → react-CorNPdie.js} +0 -0
- /package/dist/web/assets/{red-DSecpIOT.js → red-rs3HE3re.js} +0 -0
- /package/dist/web/assets/{reg-Dp30FusS.js → reg-DkTIaX7a.js} +0 -0
- /package/dist/web/assets/{regexp-DXJnEM_6.js → regexp-4TLSs4_m.js} +0 -0
- /package/dist/web/assets/{rel-Dr483ozj.js → rel-B0jumCjt.js} +0 -0
- /package/dist/web/assets/{resolve-theme-DJORYzKu.js → resolve-theme-CCoakF2E.js} +0 -0
- /package/dist/web/assets/{riscv-ZfBy5IA1.js → riscv-DtaWMlo0.js} +0 -0
- /package/dist/web/assets/{ron-ZJ_CljGP.js → ron-BCPxwJD6.js} +0 -0
- /package/dist/web/assets/{rose-pine-CcW_eJ6G.js → rose-pine-CNryoqGi.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-v2l_UF-j.js → rose-pine-dawn-C-gJOQVP.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-CNXkwf3D.js → rose-pine-moon-BdXSXpZh.js} +0 -0
- /package/dist/web/assets/{rosmsg-BMYMVW84.js → rosmsg-CPG8X-D5.js} +0 -0
- /package/dist/web/assets/{rust-BR-zJh5q.js → rust-CxfW3ePx.js} +0 -0
- /package/dist/web/assets/{sass-BukPOBFz.js → sass-D-7bxo_L.js} +0 -0
- /package/dist/web/assets/{save-DO5xjW95.js → save-BM4p9_9n.js} +0 -0
- /package/dist/web/assets/{scala-DUFY7D6D.js → scala-DyUsfHNc.js} +0 -0
- /package/dist/web/assets/{scheme-Dl-dNNxQ.js → scheme-ROgrwiO-.js} +0 -0
- /package/dist/web/assets/{sdbl-DdmlQhia.js → sdbl-B1EAQWpp.js} +0 -0
- /package/dist/web/assets/{shellscript-UyhrvGcd.js → shellscript-HhmB_Ufy.js} +0 -0
- /package/dist/web/assets/{shield-check-m2QzZTuZ.js → shield-check-C3qYSU8a.js} +0 -0
- /package/dist/web/assets/{shield-off-DNn7VzJe.js → shield-off-D5tU7gQT.js} +0 -0
- /package/dist/web/assets/{slack-dark-Cv6Szr-q.js → slack-dark-DnmSY72G.js} +0 -0
- /package/dist/web/assets/{slack-ochin-V6Ii3lAm.js → slack-ochin-q97gHjcd.js} +0 -0
- /package/dist/web/assets/{smalltalk-BUCv5G91.js → smalltalk-B-1waXvA.js} +0 -0
- /package/dist/web/assets/{snazzy-light-B67a78qi.js → snazzy-light-C8yWDN2g.js} +0 -0
- /package/dist/web/assets/{solarized-dark-ccWnfYyA.js → solarized-dark-ClVDEMJZ.js} +0 -0
- /package/dist/web/assets/{solarized-light-C3WyRO5I.js → solarized-light-Bts6CUIP.js} +0 -0
- /package/dist/web/assets/{solidity-BFbRPNi_.js → solidity-DYxwp0om.js} +0 -0
- /package/dist/web/assets/{splunk-zdpwCJLE.js → splunk-jK6-Sr06.js} +0 -0
- /package/dist/web/assets/{sql-B2IPUsfz.js → sql-DPpd8aUS.js} +0 -0
- /package/dist/web/assets/{ssh-config-Co4cXifq.js → ssh-config-Bimb9LVr.js} +0 -0
- /package/dist/web/assets/{stylus-CmI75XY8.js → stylus-B2DgJRJw.js} +0 -0
- /package/dist/web/assets/{swift-Dfks9YQI.js → swift-ChEH2nFL.js} +0 -0
- /package/dist/web/assets/{synthwave-84-B3LERzNS.js → synthwave-84-DwSLbgAm.js} +0 -0
- /package/dist/web/assets/{system-verilog-BFvldiwl.js → system-verilog-DoVBMhJ8.js} +0 -0
- /package/dist/web/assets/{systemd-Cu4Vvgbu.js → systemd--Ux4VAYD.js} +0 -0
- /package/dist/web/assets/{table-CAVmgQMT.js → table-Di8pv2Y_.js} +0 -0
- /package/dist/web/assets/{talonscript-Bnq32pUB.js → talonscript-DQNx9nak.js} +0 -0
- /package/dist/web/assets/{tasl-Nwe2K3J1.js → tasl-Bwy_Y9uD.js} +0 -0
- /package/dist/web/assets/{tcl-zOnpywrl.js → tcl-Drea-2jA.js} +0 -0
- /package/dist/web/assets/{terraform-Bc9Vj-n3.js → terraform-4rS5HoFk.js} +0 -0
- /package/dist/web/assets/{tokyo-night-9_KyM8M2.js → tokyo-night-C9MqaxIn.js} +0 -0
- /package/dist/web/assets/{toml-ByH-fxOY.js → toml-tSgOSv42.js} +0 -0
- /package/dist/web/assets/{tsv-DrCnDClP.js → tsv-BmL4O1Zx.js} +0 -0
- /package/dist/web/assets/{tsx-B8LafSYr.js → tsx-BwzeA880.js} +0 -0
- /package/dist/web/assets/{turtle-DuyP_QCJ.js → turtle-2DYvssFL.js} +0 -0
- /package/dist/web/assets/{typescript-BTBJb5RD.js → typescript-ZC-vonSh.js} +0 -0
- /package/dist/web/assets/{typespec-DdOFo7M5.js → typespec-DkCEO45F.js} +0 -0
- /package/dist/web/assets/{typst-LYvnBWQu.js → typst-DAZdb2n6.js} +0 -0
- /package/dist/web/assets/{utils-CTg5uAYR.js → utils-CSCvNZxE.js} +0 -0
- /package/dist/web/assets/{v-BuBYA1_x.js → v-B_wsb4C1.js} +0 -0
- /package/dist/web/assets/{vala-vRIBYYe3.js → vala-BDBNS784.js} +0 -0
- /package/dist/web/assets/{vb-DwGi-IYM.js → vb-CuOoJSSa.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-i6SHFTfU.js → vendor-xterm-CImj6i6Z.js} +0 -0
- /package/dist/web/assets/{verilog-6B1X4G2-.js → verilog-BCcOvOcc.js} +0 -0
- /package/dist/web/assets/{vesper-Dw1y_Ae7.js → vesper-vN8CREK5.js} +0 -0
- /package/dist/web/assets/{vhdl-DK8JwBzJ.js → vhdl-CI8PoqeM.js} +0 -0
- /package/dist/web/assets/{viml-BTKIEIqm.js → viml-B66dNP1B.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BqqolhEg.js → vitesse-black-0bGIaCfD.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-B9JL5WQA.js → vitesse-dark-Dk-RSXq-.js} +0 -0
- /package/dist/web/assets/{vitesse-light-DYJKj2se.js → vitesse-light-BUsA5RhG.js} +0 -0
- /package/dist/web/assets/{vyper-D-jDI3WH.js → vyper-dNodzEa8.js} +0 -0
- /package/dist/web/assets/{wasm-1-GlNdpk.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-BBW4WmT2.js → wasm-d6SE2_qm.js} +0 -0
- /package/dist/web/assets/{wenyan-B6cM7sDO.js → wenyan-D0h8Uz55.js} +0 -0
- /package/dist/web/assets/{wgsl-FaHGQgko.js → wgsl-vKs_vsvQ.js} +0 -0
- /package/dist/web/assets/{wikitext-DtIyPUrk.js → wikitext-BIWsPJit.js} +0 -0
- /package/dist/web/assets/{wit-BMVRif5Y.js → wit-6vmKi2H3.js} +0 -0
- /package/dist/web/assets/{wolfram-DQsCGohn.js → wolfram-zoNg6F1a.js} +0 -0
- /package/dist/web/assets/{x-C2VSAp7O.js → x-BsFLg-T8.js} +0 -0
- /package/dist/web/assets/{yaml-C-Va56Gt.js → yaml-W95qP6-B.js} +0 -0
- /package/dist/web/assets/{zenscript-DdobKGI-.js → zenscript-BxircxDx.js} +0 -0
- /package/dist/web/assets/{zig-vqDFwj6q.js → zig-DD79WQtz.js} +0 -0
|
@@ -7,16 +7,18 @@ import {
|
|
|
7
7
|
setMemberSession,
|
|
8
8
|
setGroupLeaderSession,
|
|
9
9
|
} from "./group-chat.store.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { runReplyBurst } from "./turn-engine.ts";
|
|
11
11
|
import { makeEngineRunAgent } from "./agent-runner.ts";
|
|
12
12
|
import type { ChatBackend } from "./agent-runner.ts";
|
|
13
|
+
import { makeResponderRouter } from "./responder-router.ts";
|
|
13
14
|
import { archiveAndDelete } from "./transcript-archive.ts";
|
|
14
15
|
import type {
|
|
15
16
|
Group,
|
|
16
17
|
GroupMember,
|
|
17
18
|
GroupMessage,
|
|
18
19
|
AgentTurnResult,
|
|
19
|
-
|
|
20
|
+
BurstResult,
|
|
21
|
+
TurnEngineDeps,
|
|
20
22
|
} from "../../types/group-chat.ts";
|
|
21
23
|
import type { GroupChatServerMessage } from "../../types/group-chat-ws.ts";
|
|
22
24
|
|
|
@@ -24,10 +26,9 @@ type WsClient = { send: (data: string) => void };
|
|
|
24
26
|
|
|
25
27
|
interface GroupRuntime {
|
|
26
28
|
abort: AbortController;
|
|
27
|
-
|
|
28
|
-
/** Bounded reconnect buffer of emitted server events. */
|
|
29
|
+
/** Bounded reconnect buffer of the CURRENT burst's events (replayed on reconnect). */
|
|
29
30
|
buffer: GroupChatServerMessage[];
|
|
30
|
-
loop: Promise<
|
|
31
|
+
loop: Promise<BurstResult>;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
const MAX_BUFFER = 2000;
|
|
@@ -37,31 +38,54 @@ const MAX_BUFFER = 2000;
|
|
|
37
38
|
* model — FE disconnect does NOT abort the loop. */
|
|
38
39
|
class GroupChatService {
|
|
39
40
|
private runtimes = new Map<string, GroupRuntime>();
|
|
40
|
-
/** WS clients
|
|
41
|
-
|
|
41
|
+
/** Connected WS clients per group — PERSISTENT across bursts. Must NOT be tied to a
|
|
42
|
+
* runtime's lifetime: with re-seed-per-message the runtime is created/destroyed for
|
|
43
|
+
* every message, and a client stored on the runtime would be orphaned when the burst
|
|
44
|
+
* ends (missing its own next message until it reconnects). */
|
|
45
|
+
private clients = new Map<string, Set<WsClient>>();
|
|
46
|
+
/** Latest user message received while a burst was running — drained into the next
|
|
47
|
+
* burst when the current one finishes (single-slot: newer supersedes older). */
|
|
48
|
+
private pendingMessage = new Map<string, GroupMessage>();
|
|
49
|
+
/** Member session ids known created + live THIS server run. Keep-alive: a member's
|
|
50
|
+
* session is reused (resumed → native memory) across bursts while its id is here.
|
|
51
|
+
* Cleared on server restart → sessions are recreated fresh (memory reset, accepted). */
|
|
52
|
+
private aliveSessions = new Set<string>();
|
|
42
53
|
|
|
43
54
|
// --- test seams (production uses the real provider-backed runner) --------
|
|
44
55
|
private stubbedRunAgent: (() => (m: GroupMember, prompt: string) => Promise<AgentTurnResult>) | null = null;
|
|
56
|
+
private stubbedResponderRouter: (() => NonNullable<TurnEngineDeps["routeNextSpeakers"]>) | null = null;
|
|
45
57
|
private spawnStub = false;
|
|
46
58
|
|
|
47
59
|
_setRunAgentFactory(f: () => (m: GroupMember, prompt: string) => Promise<AgentTurnResult>): void {
|
|
48
60
|
this.stubbedRunAgent = f;
|
|
49
61
|
}
|
|
62
|
+
_setResponderRouterFactory(f: (() => NonNullable<TurnEngineDeps["routeNextSpeakers"]>) | null): void {
|
|
63
|
+
this.stubbedResponderRouter = f;
|
|
64
|
+
}
|
|
50
65
|
_setSpawnStub(v: boolean): void { this.spawnStub = v; }
|
|
66
|
+
/** Inject a fake ChatBackend (session/create/send) for lifecycle tests. */
|
|
67
|
+
_setBackend(b: ChatBackend | null): void { this.backendCache = b; this.aliveSessions.clear(); }
|
|
51
68
|
isRunning(groupId: string): boolean { return this.runtimes.has(groupId); }
|
|
52
69
|
|
|
53
70
|
// --- lifecycle -----------------------------------------------------------
|
|
54
71
|
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
72
|
+
/** Handle a user message: persist it as a chat message, then run a bounded reply
|
|
73
|
+
* burst. If a burst is already running, queue the message (newer supersedes older)
|
|
74
|
+
* and run it once the current burst finishes. */
|
|
57
75
|
async start(groupId: string, userMessage: string, providerId = "claude"): Promise<void> {
|
|
58
|
-
if (this.runtimes.has(groupId)) return;
|
|
59
76
|
const { group, members } = this.loadGroup(groupId);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
const msg = appendMessage({
|
|
78
|
+
groupId, fromMember: "user", kind: "chat", summary: userMessage,
|
|
79
|
+
turnIndex: this.nextTurnIndex(groupId),
|
|
63
80
|
});
|
|
64
|
-
|
|
81
|
+
|
|
82
|
+
if (this.runtimes.has(groupId)) {
|
|
83
|
+
// A burst is in flight — queue this message and surface it in the feed now.
|
|
84
|
+
this.pendingMessage.set(groupId, msg);
|
|
85
|
+
this.emit(groupId, { type: "group_message", message: msg });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await this.runBurst(group, members, providerId, msg);
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
/** Stop a running group: abort the in-flight turn + mark paused. Cooperative
|
|
@@ -72,16 +96,17 @@ class GroupChatService {
|
|
|
72
96
|
setGroupStatus(groupId, "paused");
|
|
73
97
|
}
|
|
74
98
|
|
|
75
|
-
/** Resume: re-
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
99
|
+
/** Resume after a Stop: re-run a reply burst ONLY when the latest message is an
|
|
100
|
+
* unanswered user message (i.e. a burst was stopped/errored before replying). If the
|
|
101
|
+
* last message is an assistant reply, the conversation is already answered → no-op,
|
|
102
|
+
* so Resume never double-replies to a question that was already handled. */
|
|
79
103
|
async resume(groupId: string, providerId = "claude"): Promise<void> {
|
|
80
104
|
if (this.runtimes.has(groupId)) return;
|
|
81
105
|
const { group, members } = this.loadGroup(groupId);
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
106
|
+
const msgs = readMessages(groupId);
|
|
107
|
+
const last = msgs[msgs.length - 1];
|
|
108
|
+
if (!last || last.fromMember !== "user") { setGroupStatus(groupId, "idle"); return; }
|
|
109
|
+
await this.runBurst(group, members, providerId);
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
// --- shared loop core ----------------------------------------------------
|
|
@@ -94,71 +119,104 @@ class GroupChatService {
|
|
|
94
119
|
return { group, members };
|
|
95
120
|
}
|
|
96
121
|
|
|
97
|
-
/**
|
|
98
|
-
private
|
|
99
|
-
const
|
|
100
|
-
return
|
|
122
|
+
/** Next sequential turn index for the group's durable bus. */
|
|
123
|
+
private nextTurnIndex(groupId: string): number {
|
|
124
|
+
const prior = readMessages(groupId);
|
|
125
|
+
return (prior[prior.length - 1]?.turnIndex ?? -1) + 1;
|
|
101
126
|
}
|
|
102
127
|
|
|
103
|
-
/**
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
/** Register the runtime, then (async) spawn sessions + run one detached reply burst,
|
|
129
|
+
* draining any user message queued while the burst ran. Re-seed per message.
|
|
130
|
+
* The runtime is registered SYNCHRONOUSLY (before the first await) so a message that
|
|
131
|
+
* arrives during session spawn is queued via start()'s runtimes.has() check — never
|
|
132
|
+
* run as a second concurrent burst. */
|
|
133
|
+
private async runBurst(
|
|
134
|
+
group: Group, members: GroupMember[], providerId: string,
|
|
107
135
|
initialMessage?: GroupMessage,
|
|
108
136
|
): Promise<void> {
|
|
109
|
-
const backend = this.spawnStub ? null : await this.getBackend();
|
|
110
|
-
if (backend) await this.spawnSessions(group, members, backend);
|
|
111
|
-
|
|
112
|
-
setGroupStatus(group.id, "active");
|
|
113
137
|
const abort = new AbortController();
|
|
138
|
+
// Synchronous registration — no await may precede this (so a message arriving during
|
|
139
|
+
// session spawn is queued, not run as a second concurrent burst).
|
|
140
|
+
const runtime: GroupRuntime = {
|
|
141
|
+
abort, buffer: [],
|
|
142
|
+
loop: Promise.resolve({ reason: "stopped", turns: 0, costUsd: 0 } as BurstResult),
|
|
143
|
+
};
|
|
144
|
+
this.runtimes.set(group.id, runtime);
|
|
145
|
+
setGroupStatus(group.id, "active");
|
|
146
|
+
// Emit the user's message now that the runtime exists (so it lands in the reconnect
|
|
147
|
+
// buffer); persistent clients receive it live regardless of runtime lifetime.
|
|
148
|
+
if (initialMessage) {
|
|
149
|
+
this.emit(group.id, { type: "group_message", message: initialMessage });
|
|
150
|
+
}
|
|
114
151
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
// Dedicated ephemeral router session (cheap-model next-speaker classification) —
|
|
153
|
+
// kept separate from member sessions so router prompts never pollute member history.
|
|
154
|
+
let routerSessionId: string | null = null;
|
|
118
155
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
156
|
+
const loop = (async () => {
|
|
157
|
+
const backend = this.spawnStub ? null : await this.getBackend();
|
|
158
|
+
if (backend) await this.ensureSessions(group, members, backend);
|
|
159
|
+
const runAgent = this.stubbedRunAgent
|
|
160
|
+
? this.stubbedRunAgent()
|
|
161
|
+
: makeEngineRunAgent(backend!, providerId, abort.signal);
|
|
126
162
|
|
|
127
|
-
|
|
163
|
+
// Smart responder routing: prefer a stubbed router in tests; otherwise build one over
|
|
164
|
+
// a fresh ephemeral session. Absent → engine falls back to mention-following.
|
|
165
|
+
let routeNextSpeakers: TurnEngineDeps["routeNextSpeakers"];
|
|
166
|
+
if (this.stubbedResponderRouter) {
|
|
167
|
+
routeNextSpeakers = this.stubbedResponderRouter();
|
|
168
|
+
} else if (backend) {
|
|
169
|
+
const rs = await backend.createSession({
|
|
170
|
+
projectPath: group.projectPath, projectName: group.projectName,
|
|
171
|
+
title: `[group:${group.name}] router`,
|
|
172
|
+
});
|
|
173
|
+
routerSessionId = rs.id;
|
|
174
|
+
routeNextSpeakers = makeResponderRouter(backend, providerId, rs.id);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const deps = {
|
|
178
|
+
runAgent,
|
|
179
|
+
appendMessage,
|
|
180
|
+
readMessages,
|
|
181
|
+
onMessage: (message: GroupMessage) => this.emit(group.id, { type: "group_message", message }),
|
|
182
|
+
onTyping: (member: string) => this.emit(group.id, { type: "typing", member }),
|
|
183
|
+
routeNextSpeakers,
|
|
184
|
+
shouldStop: () => abort.signal.aborted,
|
|
185
|
+
};
|
|
186
|
+
return runReplyBurst(group, members, deps, { cap: group.maxTurns });
|
|
187
|
+
})()
|
|
128
188
|
.then((res) => {
|
|
129
189
|
this.emit(group.id, { type: "group_done", reason: res.reason, turns: res.turns, costUsd: res.costUsd });
|
|
130
190
|
// A user-requested stop keeps the group paused for a later resume;
|
|
131
|
-
//
|
|
191
|
+
// a natural burst end returns the group to idle to wait for the next message.
|
|
132
192
|
setGroupStatus(group.id, res.reason === "stopped" ? "paused" : "idle");
|
|
133
193
|
return res;
|
|
134
194
|
})
|
|
135
195
|
.catch((e) => {
|
|
136
196
|
this.emit(group.id, { type: "error", message: (e as Error).message });
|
|
137
197
|
setGroupStatus(group.id, "idle");
|
|
138
|
-
return { reason: "stopped", turns: 0, costUsd: 0 } as
|
|
198
|
+
return { reason: "stopped", turns: 0, costUsd: 0 } as BurstResult;
|
|
139
199
|
})
|
|
140
200
|
.finally(() => {
|
|
141
|
-
|
|
201
|
+
// Keep-alive: member sessions are NOT archived per burst — they persist so the
|
|
202
|
+
// next burst resumes them (native memory). Archival happens on group/member delete.
|
|
203
|
+
if (routerSessionId) void this.deleteRouterSession(providerId, routerSessionId);
|
|
142
204
|
this.runtimes.delete(group.id);
|
|
205
|
+
// Drain a message queued mid-burst → run the next burst for it.
|
|
206
|
+
const queued = this.pendingMessage.get(group.id);
|
|
207
|
+
if (queued) {
|
|
208
|
+
this.pendingMessage.delete(group.id);
|
|
209
|
+
void this.runBurst(group, members, providerId);
|
|
210
|
+
}
|
|
143
211
|
});
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
const inherited = this.pendingClients.get(group.id) ?? new Set<WsClient>();
|
|
147
|
-
this.pendingClients.delete(group.id);
|
|
148
|
-
this.runtimes.set(group.id, { abort, clients: inherited, buffer: [], loop });
|
|
149
|
-
// Emit the task message now that the runtime + client set exists, so WS
|
|
150
|
-
// clients that connected before start() was called receive it immediately.
|
|
151
|
-
if (initialMessage) {
|
|
152
|
-
this.emit(group.id, { type: "group_message", message: initialMessage });
|
|
153
|
-
}
|
|
154
|
-
// Surface spawn/immediate errors to the caller; loop errors are emitted.
|
|
212
|
+
runtime.loop = loop;
|
|
213
|
+
// Surface spawn/immediate errors to the caller; burst errors are emitted.
|
|
155
214
|
await Promise.resolve();
|
|
156
215
|
}
|
|
157
216
|
|
|
158
217
|
// --- WS wiring -----------------------------------------------------------
|
|
159
218
|
|
|
160
219
|
addClient(groupId: string, ws: WsClient): void {
|
|
161
|
-
const rt = this.runtimes.get(groupId);
|
|
162
220
|
const group = getGroup(groupId);
|
|
163
221
|
if (group) {
|
|
164
222
|
const members = listMembers(groupId).map((m) => ({
|
|
@@ -166,39 +224,50 @@ class GroupChatService {
|
|
|
166
224
|
}));
|
|
167
225
|
ws.send(JSON.stringify({ type: "group_state", groupId, status: group.status, members } satisfies GroupChatServerMessage));
|
|
168
226
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let pending = this.pendingClients.get(groupId);
|
|
177
|
-
if (!pending) { pending = new Set(); this.pendingClients.set(groupId, pending); }
|
|
178
|
-
pending.add(ws);
|
|
179
|
-
}
|
|
227
|
+
// Register in the persistent per-group client set (survives burst churn).
|
|
228
|
+
let set = this.clients.get(groupId);
|
|
229
|
+
if (!set) { set = new Set(); this.clients.set(groupId, set); }
|
|
230
|
+
set.add(ws);
|
|
231
|
+
// Replay the current burst's buffer so a mid-burst reconnect catches up.
|
|
232
|
+
const rt = this.runtimes.get(groupId);
|
|
233
|
+
if (rt) for (const ev of rt.buffer) ws.send(JSON.stringify(ev));
|
|
180
234
|
}
|
|
181
235
|
|
|
182
236
|
removeClient(groupId: string, ws: WsClient): void {
|
|
183
|
-
this.
|
|
184
|
-
|
|
237
|
+
const set = this.clients.get(groupId);
|
|
238
|
+
set?.delete(ws);
|
|
239
|
+
if (set && set.size === 0) this.clients.delete(groupId);
|
|
185
240
|
}
|
|
186
241
|
|
|
187
242
|
private emit(groupId: string, ev: GroupChatServerMessage): void {
|
|
243
|
+
// Buffer into the current burst (for reconnect replay) when one is running.
|
|
188
244
|
const rt = this.runtimes.get(groupId);
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
245
|
+
if (rt) {
|
|
246
|
+
rt.buffer.push(ev);
|
|
247
|
+
if (rt.buffer.length > MAX_BUFFER) rt.buffer.shift();
|
|
248
|
+
}
|
|
249
|
+
// Broadcast to all persistent clients — independent of runtime lifetime.
|
|
250
|
+
const set = this.clients.get(groupId);
|
|
251
|
+
if (!set) return;
|
|
192
252
|
const payload = JSON.stringify(ev);
|
|
193
|
-
for (const c of
|
|
253
|
+
for (const c of set) {
|
|
194
254
|
try { c.send(payload); } catch { /* dropped client */ }
|
|
195
255
|
}
|
|
196
256
|
}
|
|
197
257
|
|
|
198
258
|
// --- provider-backed session lifecycle -----------------------------------
|
|
199
259
|
|
|
200
|
-
|
|
260
|
+
/** Keep-alive: reuse a member's session while it's live this run (resume → native
|
|
261
|
+
* memory); otherwise create a fresh one (archiving any stale prior file first). */
|
|
262
|
+
private async ensureSessions(group: Group, members: GroupMember[], backend: ChatBackend): Promise<void> {
|
|
201
263
|
for (const m of members) {
|
|
264
|
+
if (m.sessionId && this.aliveSessions.has(m.sessionId)) {
|
|
265
|
+
if (m.role === "leader") setGroupLeaderSession(group.id, m.sessionId);
|
|
266
|
+
continue; // reuse — the provider resumes it (in-process message count > 0)
|
|
267
|
+
}
|
|
268
|
+
// Not alive this run (new member / post-restart): archive any orphaned old file,
|
|
269
|
+
// then create a fresh session so we never create-with-an-existing-id.
|
|
270
|
+
if (m.sessionId) await archiveAndDelete(m.sessionId, group.name).catch(() => {});
|
|
202
271
|
const session = await backend.createSession({
|
|
203
272
|
projectPath: group.projectPath,
|
|
204
273
|
projectName: group.projectName,
|
|
@@ -206,16 +275,46 @@ class GroupChatService {
|
|
|
206
275
|
});
|
|
207
276
|
setMemberSession(m.id, session.id);
|
|
208
277
|
m.sessionId = session.id;
|
|
278
|
+
this.aliveSessions.add(session.id);
|
|
209
279
|
if (m.role === "leader") setGroupLeaderSession(group.id, session.id);
|
|
210
280
|
}
|
|
211
281
|
}
|
|
212
282
|
|
|
283
|
+
/** Archive + delete every member's live session and forget it (on group/member delete). */
|
|
213
284
|
private async archiveMembers(group: Group, members: GroupMember[]): Promise<void> {
|
|
214
285
|
for (const m of members) {
|
|
215
|
-
if (m.sessionId)
|
|
286
|
+
if (m.sessionId) {
|
|
287
|
+
await archiveAndDelete(m.sessionId, group.name).catch(() => {});
|
|
288
|
+
this.aliveSessions.delete(m.sessionId);
|
|
289
|
+
}
|
|
216
290
|
}
|
|
217
291
|
}
|
|
218
292
|
|
|
293
|
+
/** Teardown for group delete: stop any burst, archive all member sessions, drop state. */
|
|
294
|
+
async archiveAndForget(groupId: string): Promise<void> {
|
|
295
|
+
const group = getGroup(groupId);
|
|
296
|
+
if (!group) return;
|
|
297
|
+
this.stop(groupId);
|
|
298
|
+
if (!this.spawnStub) await this.archiveMembers(group, listMembers(groupId));
|
|
299
|
+
this.clients.delete(groupId);
|
|
300
|
+
this.pendingMessage.delete(groupId);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Archive + forget a single member's session (on member removal). */
|
|
304
|
+
async archiveMemberSession(groupName: string, sessionId: string | null): Promise<void> {
|
|
305
|
+
if (!sessionId || this.spawnStub) return;
|
|
306
|
+
await archiveAndDelete(sessionId, groupName).catch(() => {});
|
|
307
|
+
this.aliveSessions.delete(sessionId);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Delete the ephemeral router session (no archive — its transcript is throwaway). */
|
|
311
|
+
private async deleteRouterSession(providerId: string, sessionId: string): Promise<void> {
|
|
312
|
+
try {
|
|
313
|
+
const { chatService } = await import("../chat.service.ts");
|
|
314
|
+
await chatService.deleteSession(providerId, sessionId);
|
|
315
|
+
} catch { /* best-effort cleanup */ }
|
|
316
|
+
}
|
|
317
|
+
|
|
219
318
|
private backendCache: ChatBackend | null = null;
|
|
220
319
|
|
|
221
320
|
private async getBackend(): Promise<ChatBackend> {
|
|
@@ -228,7 +327,7 @@ class GroupChatService {
|
|
|
228
327
|
// consumer-side signal guard halts the turn, so only permissionMode is
|
|
229
328
|
// forwarded here.
|
|
230
329
|
sendMessage: (pid, sid, prompt, opts) =>
|
|
231
|
-
chatService.sendMessage(pid, sid, prompt, { permissionMode: opts?.permissionMode }),
|
|
330
|
+
chatService.sendMessage(pid, sid, prompt, { permissionMode: opts?.permissionMode, model: opts?.model, oneMContext: opts?.oneMContext }),
|
|
232
331
|
};
|
|
233
332
|
return this.backendCache;
|
|
234
333
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type MemberStatus,
|
|
11
11
|
type CreateGroupInput,
|
|
12
12
|
type AddMemberInput,
|
|
13
|
+
type UpdateMemberInput,
|
|
13
14
|
type AppendMessageInput,
|
|
14
15
|
type ReadMessagesOptions,
|
|
15
16
|
} from "../../types/group-chat.ts";
|
|
@@ -142,6 +143,11 @@ export function setGroupLeaderSession(id: string, sessionId: string): void {
|
|
|
142
143
|
getDb().query("UPDATE chat_groups SET leader_session_id = ? WHERE id = ?").run(sessionId, id);
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/** Update the per-group reply-burst cap (max AI turns per user message). */
|
|
147
|
+
export function setGroupMaxTurns(id: string, maxTurns: number): void {
|
|
148
|
+
getDb().query("UPDATE chat_groups SET max_turns = ? WHERE id = ?").run(maxTurns, id);
|
|
149
|
+
}
|
|
150
|
+
|
|
145
151
|
// ---------------------------------------------------------------------------
|
|
146
152
|
// Members
|
|
147
153
|
// ---------------------------------------------------------------------------
|
|
@@ -159,6 +165,30 @@ export function addMember(input: AddMemberInput): GroupMember {
|
|
|
159
165
|
return toMember(row);
|
|
160
166
|
}
|
|
161
167
|
|
|
168
|
+
export function getMember(memberId: string): GroupMember | null {
|
|
169
|
+
const row = getDb().query("SELECT * FROM chat_group_members WHERE id = ?").get(memberId) as MemberRow | null;
|
|
170
|
+
return row ? toMember(row) : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Update the provided fields of a member; unspecified fields are left unchanged. */
|
|
174
|
+
export function updateMember(memberId: string, patch: UpdateMemberInput): GroupMember | null {
|
|
175
|
+
const cols: Array<[string, string | null]> = [];
|
|
176
|
+
if (patch.role !== undefined) cols.push(["role", patch.role]);
|
|
177
|
+
if (patch.name !== undefined) cols.push(["name", patch.name]);
|
|
178
|
+
if (patch.persona !== undefined) cols.push(["persona", patch.persona]);
|
|
179
|
+
if (patch.agentType !== undefined) cols.push(["agent_type", patch.agentType]);
|
|
180
|
+
if (patch.model !== undefined) cols.push(["model", patch.model]);
|
|
181
|
+
if (patch.color !== undefined) cols.push(["color", patch.color]);
|
|
182
|
+
if (cols.length === 0) return getMember(memberId);
|
|
183
|
+
const set = cols.map(([c]) => `${c} = ?`).join(", ");
|
|
184
|
+
getDb().query(`UPDATE chat_group_members SET ${set} WHERE id = ?`).run(...cols.map(([, v]) => v), memberId);
|
|
185
|
+
return getMember(memberId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function removeMember(memberId: string): void {
|
|
189
|
+
getDb().query("DELETE FROM chat_group_members WHERE id = ?").run(memberId);
|
|
190
|
+
}
|
|
191
|
+
|
|
162
192
|
export function listMembers(groupId: string): GroupMember[] {
|
|
163
193
|
const rows = getDb().query(
|
|
164
194
|
"SELECT * FROM chat_group_members WHERE group_id = ? ORDER BY joined_at, id",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { GroupMember, GroupMessage } from "../../types/group-chat.ts";
|
|
2
|
+
import { buildContextWindow, renderWindow, DEFAULT_WINDOW } from "./context-window.ts";
|
|
3
|
+
|
|
4
|
+
/** Cheapest/fastest model for the lightweight next-speaker classification. */
|
|
5
|
+
export const ROUTER_MODEL = "claude-haiku-4-5";
|
|
6
|
+
const NONE = "NONE";
|
|
7
|
+
|
|
8
|
+
/** Minimal backend the router needs (structural subset of agent-runner's ChatBackend). */
|
|
9
|
+
export interface RouterBackend {
|
|
10
|
+
sendMessage(
|
|
11
|
+
providerId: string,
|
|
12
|
+
sessionId: string,
|
|
13
|
+
prompt: string,
|
|
14
|
+
opts?: { model?: string; permissionMode?: string; oneMContext?: boolean },
|
|
15
|
+
): AsyncIterable<{ type: string; content?: string; message?: string }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function escapeRe(s: string): string {
|
|
19
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Parse the router's reply into known member names (0-N), in order of appearance.
|
|
23
|
+
* Never invents a name — only exact member-name matches (case-insensitive, word-bounded).
|
|
24
|
+
* Longest names first + claimed spans so a short name can't match inside a longer one.
|
|
25
|
+
* NONE / unknown / garbage → []. */
|
|
26
|
+
export function parseResponders(text: string, members: GroupMember[]): string[] {
|
|
27
|
+
const t = (text ?? "").trim();
|
|
28
|
+
if (!t) return [];
|
|
29
|
+
const byLen = [...members].sort((a, b) => b.name.length - a.name.length);
|
|
30
|
+
const claimed: Array<[number, number]> = [];
|
|
31
|
+
const found: Array<{ name: string; at: number }> = [];
|
|
32
|
+
for (const m of byLen) {
|
|
33
|
+
if (!m.name) continue;
|
|
34
|
+
const re = new RegExp(`(^|[^\\p{L}\\p{N}_])(${escapeRe(m.name)})([^\\p{L}\\p{N}_]|$)`, "giu");
|
|
35
|
+
let match: RegExpExecArray | null;
|
|
36
|
+
while ((match = re.exec(t)) !== null) {
|
|
37
|
+
const start = match.index + match[1]!.length;
|
|
38
|
+
if (claimed.some(([s, e]) => start >= s && start < e)) continue; // inside a longer name
|
|
39
|
+
found.push({ name: m.name, at: start });
|
|
40
|
+
claimed.push([start, start + m.name.length]);
|
|
41
|
+
break; // one occurrence per member is enough
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
found.sort((a, b) => a.at - b.at);
|
|
45
|
+
const out: string[] = [];
|
|
46
|
+
const seen = new Set<string>();
|
|
47
|
+
for (const f of found) if (!seen.has(f.name)) { out.push(f.name); seen.add(f.name); }
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Build the classification prompt. Priority: recent history > member name > persona;
|
|
52
|
+
* leader only as fallback / to curb off-topic drift; user turns must pick someone. */
|
|
53
|
+
export function buildRouterPrompt(
|
|
54
|
+
history: GroupMessage[], members: GroupMember[], isUserTurn: boolean,
|
|
55
|
+
): string {
|
|
56
|
+
const { window, rollingSummary } = buildContextWindow(history, DEFAULT_WINDOW);
|
|
57
|
+
const leader = members.find((m) => m.role === "leader");
|
|
58
|
+
const roster = members
|
|
59
|
+
.map((m) => `- ${m.name}${m.role === "leader" ? " (leader)" : ""}${m.persona ? `: ${m.persona}` : ""}`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
const names = members.map((m) => m.name).join(", ");
|
|
62
|
+
// The AI the user is currently conversing with (spoke most recently).
|
|
63
|
+
const lastSpeaker = [...history].reverse().find((m) => m.fromMember !== "user")?.fromMember;
|
|
64
|
+
|
|
65
|
+
const parts = [
|
|
66
|
+
`You route a group chat: decide WHO should speak next. Output member names (or ${NONE}).`,
|
|
67
|
+
`Members:\n${roster}`,
|
|
68
|
+
rollingSummary ? `\nEARLIER (summary):\n${rollingSummary}` : "",
|
|
69
|
+
`\nRECENT MESSAGES:\n${renderWindow(window)}`,
|
|
70
|
+
`\nRules (priority order):`,
|
|
71
|
+
lastSpeaker
|
|
72
|
+
? `1. CONTINUITY: the user is currently talking with "${lastSpeaker}" (spoke most recently). If this new user message is a reply or instruction directed at them (incl. "assign X", "tell the others", "you do it"), pick "${lastSpeaker}" — even if that's the leader. Only pick someone else if the message clearly targets a different member.`
|
|
73
|
+
: `1. Prefer whoever the RECENT conversation makes most relevant — continue an ongoing thread.`,
|
|
74
|
+
`2. Otherwise, the member NAME being addressed.`,
|
|
75
|
+
`3. Use PERSONA/expertise only for generic questions with no clear thread.`,
|
|
76
|
+
`4. Pick the leader (${leader?.name ?? "leader"}) ONLY if no member clearly fits, or to steer the team back when the discussion drifts off-topic.`,
|
|
77
|
+
`5. PARALLELISM: if several members should respond and can do so INDEPENDENTLY (their replies don't depend on each other), list them ALL — they run in parallel. If a reply must build on another's, list only the ONE who goes next.`,
|
|
78
|
+
`6. WRAP-UP: after the leader (${leader?.name ?? "leader"}) delegated a task and the assigned members have reported back, pick the leader ONCE to summarize the results for the user — then, on the following turn, output ${NONE} to end.`,
|
|
79
|
+
isUserTurn
|
|
80
|
+
? `This is a reply to the USER — you MUST pick at least one member (never ${NONE}).`
|
|
81
|
+
: `If no one has anything useful to add, output ${NONE} to end the exchange.`,
|
|
82
|
+
`\nAnswer with member name(s) from [${names}]${isUserTurn ? "" : ` or ${NONE}`}, separated by spaces — no other words.`,
|
|
83
|
+
];
|
|
84
|
+
return parts.filter(Boolean).join("\n");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Bind a `routeNextSpeaker` fn to a backend + a dedicated router session. Best-effort:
|
|
88
|
+
* any error / empty / unknown reply → null (the engine forces the leader on user turns
|
|
89
|
+
* and ends the burst on AI turns). Uses the cheapest model via a per-call override. */
|
|
90
|
+
export function makeResponderRouter(
|
|
91
|
+
backend: RouterBackend, providerId: string, sessionId: string,
|
|
92
|
+
opts: { model?: string } = {},
|
|
93
|
+
): (ctx: { history: GroupMessage[]; members: GroupMember[]; isUserTurn: boolean }) => Promise<string[]> {
|
|
94
|
+
const model = opts.model ?? ROUTER_MODEL;
|
|
95
|
+
return async ({ history, members, isUserTurn }) => {
|
|
96
|
+
try {
|
|
97
|
+
const prompt = buildRouterPrompt(history, members, isUserTurn);
|
|
98
|
+
let text = "";
|
|
99
|
+
// oneMContext:false — the cheap router model needs no 1M window and may not support it.
|
|
100
|
+
for await (const ev of backend.sendMessage(providerId, sessionId, prompt, { model, permissionMode: "bypassPermissions", oneMContext: false })) {
|
|
101
|
+
if (ev.type === "text") text += ev.content ?? "";
|
|
102
|
+
else if (ev.type === "error") return [];
|
|
103
|
+
else if (ev.type === "done") break;
|
|
104
|
+
}
|
|
105
|
+
return parseResponders(text, members);
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -74,3 +74,21 @@ export function readArchivedTranscript(sessionId: string, groupName: string): st
|
|
|
74
74
|
if (!existsSync(dest)) return null;
|
|
75
75
|
try { return readFileSync(dest, "utf8"); } catch { return null; }
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/** Absolute path of an archived transcript JSONL if it exists (else null). */
|
|
79
|
+
export function archivedTranscriptPath(sessionId: string, groupName: string): string | null {
|
|
80
|
+
const dest = join(archiveDir(groupName), `${sessionId}.jsonl`);
|
|
81
|
+
return existsSync(dest) ? dest : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Absolute path of a LIVE (not-yet-archived) session JSONL under the Claude projects
|
|
85
|
+
* root, else null. Keep-alive sessions stay here until the group is deleted. */
|
|
86
|
+
export async function liveTranscriptPath(sessionId: string): Promise<string | null> {
|
|
87
|
+
return findJsonl(sessionId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Resolve a member transcript: prefer the LIVE session file (keep-alive), fall back to
|
|
91
|
+
* the archived copy (post group/member delete). */
|
|
92
|
+
export async function resolveTranscriptPath(sessionId: string, groupName: string): Promise<string | null> {
|
|
93
|
+
return (await liveTranscriptPath(sessionId)) ?? archivedTranscriptPath(sessionId, groupName);
|
|
94
|
+
}
|