@prismshadow/penguin-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/api/types.d.ts +958 -0
- package/dist/api/types.js +1 -0
- package/dist/api/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6867 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/web-dist/assets/abap-BdImnpbu.js +1 -0
- package/web-dist/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/web-dist/assets/ada-bCR0ucgS.js +1 -0
- package/web-dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/web-dist/assets/angular-html-DA-rfuFy.js +1 -0
- package/web-dist/assets/angular-ts-BrjP3tb8.js +1 -0
- package/web-dist/assets/apache-Pmp26Uib.js +1 -0
- package/web-dist/assets/apex-Dqspr-GT.js +1 -0
- package/web-dist/assets/apl-CORt7UWP.js +1 -0
- package/web-dist/assets/applescript-Co6uUVPk.js +1 -0
- package/web-dist/assets/ara-BRHolxvo.js +1 -0
- package/web-dist/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/web-dist/assets/asm-D_Q5rh1f.js +1 -0
- package/web-dist/assets/astro-HNnZUWAn.js +1 -0
- package/web-dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/web-dist/assets/awk-DMzUqQB5.js +1 -0
- package/web-dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/web-dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/web-dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/web-dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/web-dist/assets/bat-BkioyH1T.js +1 -0
- package/web-dist/assets/beancount-k_qm7-4y.js +1 -0
- package/web-dist/assets/berry-uYugtg8r.js +1 -0
- package/web-dist/assets/bibtex-CHM0blh-.js +1 -0
- package/web-dist/assets/bicep-Bmn6On1c.js +1 -0
- package/web-dist/assets/bird2-BIv1doCn.js +1 -0
- package/web-dist/assets/blade-2xfisSek.js +1 -0
- package/web-dist/assets/bsl-BO_Y6i37.js +1 -0
- package/web-dist/assets/c-BIGW1oBm.js +1 -0
- package/web-dist/assets/c3-MRO5bC_T.js +1 -0
- package/web-dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/web-dist/assets/cairo-KRGpt6FW.js +1 -0
- package/web-dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/web-dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/web-dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/web-dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/web-dist/assets/clarity-D53aC0YG.js +1 -0
- package/web-dist/assets/clojure-P80f7IUj.js +1 -0
- package/web-dist/assets/cmake-D1j8_8rp.js +1 -0
- package/web-dist/assets/cobol-nBiQ_Alo.js +1 -0
- package/web-dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/web-dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/web-dist/assets/coffee-Ch7k5sss.js +1 -0
- package/web-dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/web-dist/assets/coq-DkFqJrB1.js +1 -0
- package/web-dist/assets/cpp-UfJy6YNI.js +1 -0
- package/web-dist/assets/crystal-DGywbUpC.js +1 -0
- package/web-dist/assets/csharp-DSvCPggb.js +1 -0
- package/web-dist/assets/css-CLj8gQPS.js +1 -0
- package/web-dist/assets/csv-fuZLfV_i.js +1 -0
- package/web-dist/assets/cue-D82EKSYY.js +1 -0
- package/web-dist/assets/cypher-COkxafJQ.js +1 -0
- package/web-dist/assets/d-85-TOEBH.js +1 -0
- package/web-dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/web-dist/assets/dart-bE4Kk8sk.js +1 -0
- package/web-dist/assets/dax-CEL-wOlO.js +1 -0
- package/web-dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/web-dist/assets/diff-D97Zzqfu.js +1 -0
- package/web-dist/assets/docker-BcOcwvcX.js +1 -0
- package/web-dist/assets/dotenv-Da5cRb03.js +1 -0
- package/web-dist/assets/dracula-BzJJZx-M.js +1 -0
- package/web-dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/web-dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/web-dist/assets/edge-FbVlp4U3.js +1 -0
- package/web-dist/assets/elixir-CkH2-t6x.js +1 -0
- package/web-dist/assets/elm-DbKCFpqz.js +1 -0
- package/web-dist/assets/emacs-lisp-CXvaQtF9.js +1 -0
- package/web-dist/assets/erb-Dm6A9KJ5.js +1 -0
- package/web-dist/assets/erlang-DsQrWhSR.js +1 -0
- package/web-dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/web-dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/web-dist/assets/fennel-BYunw83y.js +1 -0
- package/web-dist/assets/fish-BvzEVeQv.js +1 -0
- package/web-dist/assets/fluent-C4IJs8-o.js +1 -0
- package/web-dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/web-dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/web-dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/web-dist/assets/gdresource-BOOCDP_w.js +1 -0
- package/web-dist/assets/gdscript-C5YyOfLZ.js +1 -0
- package/web-dist/assets/gdshader-DkwncUOv.js +1 -0
- package/web-dist/assets/genie-D0YGMca9.js +1 -0
- package/web-dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/web-dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/web-dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/web-dist/assets/github-dark-DHJKELXO.js +1 -0
- package/web-dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/web-dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/web-dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/web-dist/assets/github-light-DAi9KRSo.js +1 -0
- package/web-dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/web-dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/web-dist/assets/gleam-BspZqrRM.js +1 -0
- package/web-dist/assets/glimmer-js-ByusRIyA.js +1 -0
- package/web-dist/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/web-dist/assets/glsl-DplSGwfg.js +1 -0
- package/web-dist/assets/gn-n2N0HUVH.js +1 -0
- package/web-dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/web-dist/assets/go-C27-OAKa.js +1 -0
- package/web-dist/assets/graphql-ChdNCCLP.js +1 -0
- package/web-dist/assets/groovy-gcz8RCvz.js +1 -0
- package/web-dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/web-dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/web-dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/web-dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/web-dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/web-dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/web-dist/assets/hack-DbPARsA_.js +1 -0
- package/web-dist/assets/haml-D5jkg6IW.js +1 -0
- package/web-dist/assets/handlebars-BpdQsYii.js +1 -0
- package/web-dist/assets/haskell-Df6bDoY_.js +1 -0
- package/web-dist/assets/haxe-CzTSHFRz.js +1 -0
- package/web-dist/assets/hcl-BWvSN4gD.js +1 -0
- package/web-dist/assets/hjson-D5-asLiD.js +1 -0
- package/web-dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/web-dist/assets/horizon-BUw7H-hv.js +1 -0
- package/web-dist/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/web-dist/assets/houston-DnULxvSX.js +1 -0
- package/web-dist/assets/html-derivative-DlHx6ybY.js +1 -0
- package/web-dist/assets/html-pp8916En.js +1 -0
- package/web-dist/assets/http-jrhK8wxY.js +1 -0
- package/web-dist/assets/hurl-irOxFIW8.js +1 -0
- package/web-dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/web-dist/assets/hy-DFXneXwc.js +1 -0
- package/web-dist/assets/imba-DGztddWO.js +1 -0
- package/web-dist/assets/index-7oqK2MPp.js +153 -0
- package/web-dist/assets/index-DGY2Myoq.js +51 -0
- package/web-dist/assets/index-SaWO9NKB.css +1 -0
- package/web-dist/assets/ini-BEwlwnbL.js +1 -0
- package/web-dist/assets/java-CylS5w8V.js +1 -0
- package/web-dist/assets/javascript-wDzz0qaB.js +1 -0
- package/web-dist/assets/jinja-f2NsQr07.js +1 -0
- package/web-dist/assets/jison-wvAkD_A8.js +1 -0
- package/web-dist/assets/json-Cp-IABpG.js +1 -0
- package/web-dist/assets/json5-C9tS-k6U.js +1 -0
- package/web-dist/assets/jsonc-Des-eS-w.js +1 -0
- package/web-dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/web-dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/web-dist/assets/jssm-C2t-YnRu.js +1 -0
- package/web-dist/assets/jsx-g9-lgVsj.js +1 -0
- package/web-dist/assets/julia-D7OTSIA_.js +1 -0
- package/web-dist/assets/just-CUsbIsdP.js +1 -0
- package/web-dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/web-dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/web-dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/web-dist/assets/kdl-DV7GczEv.js +1 -0
- package/web-dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/web-dist/assets/kusto-wEQ09or8.js +1 -0
- package/web-dist/assets/laserwave-DUszq2jm.js +1 -0
- package/web-dist/assets/latex-CaSxy8MP.js +1 -0
- package/web-dist/assets/lean-BZvkOJ9d.js +1 -0
- package/web-dist/assets/less-B1dDrJ26.js +1 -0
- package/web-dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/web-dist/assets/liquid-C0sCDyMI.js +1 -0
- package/web-dist/assets/llvm-DjAJT7YJ.js +1 -0
- package/web-dist/assets/log-2UxHyX5q.js +1 -0
- package/web-dist/assets/logo-BtOb2qkB.js +1 -0
- package/web-dist/assets/lua-BaeVxFsk.js +1 -0
- package/web-dist/assets/luau-KW6xsasC.js +1 -0
- package/web-dist/assets/make-CHLpvVh8.js +1 -0
- package/web-dist/assets/markdown-Cvjx9yec.js +1 -0
- package/web-dist/assets/marko-DjSrsDqO.js +1 -0
- package/web-dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/web-dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/web-dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/web-dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/web-dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/web-dist/assets/matlab-D7o27uSR.js +1 -0
- package/web-dist/assets/mdc-DTYItulj.js +1 -0
- package/web-dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/web-dist/assets/mermaid-mWjccvbQ.js +1 -0
- package/web-dist/assets/min-dark-CafNBF8u.js +1 -0
- package/web-dist/assets/min-light-CTRr51gU.js +1 -0
- package/web-dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/web-dist/assets/mojo-rZm6bMo-.js +1 -0
- package/web-dist/assets/monokai-D4h5O-jR.js +1 -0
- package/web-dist/assets/moonbit-_H4v1dQx.js +1 -0
- package/web-dist/assets/move-IF9eRakj.js +1 -0
- package/web-dist/assets/narrat-DRg8JJMk.js +1 -0
- package/web-dist/assets/nextflow-C-mBbutL.js +1 -0
- package/web-dist/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/web-dist/assets/nginx-BpAMiNFr.js +1 -0
- package/web-dist/assets/night-owl-C39BiMTA.js +1 -0
- package/web-dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/web-dist/assets/nim-BIad80T-.js +1 -0
- package/web-dist/assets/nix-CwoSXNpI.js +1 -0
- package/web-dist/assets/nord-Ddv68eIx.js +1 -0
- package/web-dist/assets/nushell-Cz2AlsmD.js +1 -0
- package/web-dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/web-dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/web-dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/web-dist/assets/odin-BBf5iR-q.js +1 -0
- package/web-dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/web-dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/web-dist/assets/openscad-C4EeE6gA.js +1 -0
- package/web-dist/assets/pascal-D93ZcfNL.js +1 -0
- package/web-dist/assets/perl-B9cMNwum.js +1 -0
- package/web-dist/assets/php-Csjmro_R.js +1 -0
- package/web-dist/assets/pkl-u5AG7uiY.js +1 -0
- package/web-dist/assets/plastic-3e1v2bzS.js +1 -0
- package/web-dist/assets/plsql-ChMvpjG-.js +1 -0
- package/web-dist/assets/po-BTJTHyun.js +1 -0
- package/web-dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/web-dist/assets/polar-C0HS_06l.js +1 -0
- package/web-dist/assets/postcss-CXtECtnM.js +1 -0
- package/web-dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/web-dist/assets/powershell-Dpen1YoG.js +1 -0
- package/web-dist/assets/prisma-Dd19v3D-.js +1 -0
- package/web-dist/assets/prolog-CbFg5uaA.js +1 -0
- package/web-dist/assets/proto-C7zT0LnQ.js +1 -0
- package/web-dist/assets/pug-DKIMFp6K.js +1 -0
- package/web-dist/assets/puppet-BMWR74SV.js +1 -0
- package/web-dist/assets/purescript-CklMAg4u.js +1 -0
- package/web-dist/assets/python-B6aJPvgy.js +1 -0
- package/web-dist/assets/qml-3beO22l8.js +1 -0
- package/web-dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/web-dist/assets/qss-IeuSbFQv.js +1 -0
- package/web-dist/assets/r-Dspwwk_N.js +1 -0
- package/web-dist/assets/racket-BqYA7rlc.js +1 -0
- package/web-dist/assets/raku-DXvB9xmW.js +1 -0
- package/web-dist/assets/razor-BjBPvh-w.js +1 -0
- package/web-dist/assets/red-bN70gL4F.js +1 -0
- package/web-dist/assets/reg-C-SQnVFl.js +1 -0
- package/web-dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/web-dist/assets/rel-C3B-1QV4.js +1 -0
- package/web-dist/assets/riscv-BM1_JUlF.js +1 -0
- package/web-dist/assets/ron-D8l8udqQ.js +1 -0
- package/web-dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/web-dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/web-dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/web-dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/web-dist/assets/rst-CpCqk9r5.js +1 -0
- package/web-dist/assets/ruby-DyJCeAvU.js +1 -0
- package/web-dist/assets/rust-B1yitclQ.js +1 -0
- package/web-dist/assets/sas-DEy46yEz.js +1 -0
- package/web-dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/web-dist/assets/scala-C151Ov-r.js +1 -0
- package/web-dist/assets/scheme-C98Dy4si.js +1 -0
- package/web-dist/assets/scss-D5BDwBP9.js +1 -0
- package/web-dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/web-dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/web-dist/assets/shellscript-Yzrsuije.js +1 -0
- package/web-dist/assets/shellsession-BADoaaVG.js +1 -0
- package/web-dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/web-dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/web-dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/web-dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/web-dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/web-dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/web-dist/assets/solidity-rGO070M0.js +1 -0
- package/web-dist/assets/soy-8wufbnw4.js +1 -0
- package/web-dist/assets/sparql-rVzFXLq3.js +1 -0
- package/web-dist/assets/splunk-BtCnVYZw.js +1 -0
- package/web-dist/assets/sql-CRqJ_cUM.js +1 -0
- package/web-dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/web-dist/assets/stata-DI20mbqo.js +1 -0
- package/web-dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/web-dist/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/web-dist/assets/svelte-Cy7k_4gC.js +1 -0
- package/web-dist/assets/swift-D82vCrfD.js +1 -0
- package/web-dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/web-dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/web-dist/assets/systemd-4A_iFExJ.js +1 -0
- package/web-dist/assets/talonscript-CkByrt1z.js +1 -0
- package/web-dist/assets/tasl-QIJgUcNo.js +1 -0
- package/web-dist/assets/tcl-dwOrl1Do.js +1 -0
- package/web-dist/assets/templ-DhtptRzy.js +1 -0
- package/web-dist/assets/terraform-BETggiCN.js +1 -0
- package/web-dist/assets/tex-idrVyKtj.js +1 -0
- package/web-dist/assets/tokyo-night-hegEt444.js +1 -0
- package/web-dist/assets/toml-vGWfd6FD.js +1 -0
- package/web-dist/assets/ts-tags-D351s5mN.js +1 -0
- package/web-dist/assets/tsv-B_m7g4N7.js +1 -0
- package/web-dist/assets/tsx-COt5Ahok.js +1 -0
- package/web-dist/assets/turtle-BsS91CYL.js +1 -0
- package/web-dist/assets/twig-CW1WmMYd.js +1 -0
- package/web-dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/web-dist/assets/typespec-CAFt9gP4.js +1 -0
- package/web-dist/assets/typst-DHCkPAjA.js +1 -0
- package/web-dist/assets/v-BcVCzyr7.js +1 -0
- package/web-dist/assets/vala-CsfeWuGM.js +1 -0
- package/web-dist/assets/vb-D17OF-Vu.js +1 -0
- package/web-dist/assets/verilog-BQ8w6xss.js +1 -0
- package/web-dist/assets/vesper-DRje8inN.js +1 -0
- package/web-dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/web-dist/assets/viml-CJc9bBzg.js +1 -0
- package/web-dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/web-dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/web-dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/web-dist/assets/vue-D2xRrEX4.js +1 -0
- package/web-dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/web-dist/assets/vue-vine-BoDAl6tE.js +1 -0
- package/web-dist/assets/vyper-CDx5xZoG.js +1 -0
- package/web-dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/web-dist/assets/wasm-MzD3tlZU.js +1 -0
- package/web-dist/assets/wenyan-BV7otONQ.js +1 -0
- package/web-dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/web-dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/web-dist/assets/wit-5i3qLPDT.js +1 -0
- package/web-dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/web-dist/assets/xml-sdJ4AIDG.js +1 -0
- package/web-dist/assets/xsl-CtQFsRM5.js +1 -0
- package/web-dist/assets/yaml-Buea-lGh.js +1 -0
- package/web-dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/web-dist/assets/zig-VOosw3JB.js +1 -0
- package/web-dist/index.html +16 -0
- package/web-dist/penguin-logo.svg +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/app.ts","../src/db/database.ts","../src/db/schema.ts","../src/db/repos/agents.ts","../src/db/repos/auth-sessions.ts","../src/db/repos/errors.ts","../src/db/repos/members.ts","../src/db/repos/projects.ts","../src/db/repos/schedules.ts","../src/db/repos/sessions.ts","../src/db/repos/ui-prefs.ts","../src/db/repos/usage.ts","../src/db/repos/users.ts","../src/auth/middleware.ts","../src/http/errors.ts","../src/auth/service.ts","../src/auth/password.ts","../src/http/routes/admin.ts","../src/http/validate.ts","../src/http/routes/auth.ts","../src/http/routes/me.ts","../src/http/routes/events.ts","../src/http/sse.ts","../src/http/routes/projects.ts","../src/http/routes/members.ts","../src/http/routes/models.ts","../src/http/routes/vault.ts","../src/http/routes/schedules.ts","../src/runtime/schedule-file.ts","../src/runtime/schedule-store.ts","../src/http/routes/benchmarks.ts","../src/http/routes/skills.ts","../src/http/routes/agent-transfer.ts","../src/http/routes/agents.ts","../src/http/settle.ts","../src/http/routes/dirs.ts","../src/http/routes/agent-config.ts","../src/http/routes/agent-traces.ts","../src/http/routes/usage.ts","../src/http/routes/sessions.ts","../src/services/workspace-guard.ts","../src/services/workspace-files-service.ts","../src/runtime/channel.ts","../src/internal/dates.ts","../src/runtime/error-recorder.ts","../src/runtime/session-manager.ts","../src/runtime/approvals.ts","../src/runtime/stream-error-watcher.ts","../src/runtime/scheduler.ts","../src/runtime/title-generator.ts","../src/runtime/usage-recorder.ts","../src/services/ids.ts","../src/services/admin-service.ts","../src/services/agent-config-service.ts","../src/services/project-config-service.ts","../src/services/agent-service.ts","../src/services/benchmark-service.ts","../src/services/snapshot-service.ts","../src/services/project-service.ts","../src/services/session-service.ts","../src/services/trace-service.ts","../src/services/usage-service.ts","../src/config.ts"],"sourcesContent":["/**\n * Server startup entry point: dotenv → config → assembly → listen\n * → graceful shutdown.\n *\n * SIGINT / SIGTERM: interrupt all active runs (pending approvals converge to deny), wait\n * ≤5s for wrap-up, then close HTTP and SQLite. Tests never go through this file\n * (injected via app.request() instead).\n * There's also a process-level error fallback (uncaughtException / unhandledRejection):\n * persist + log, with the fatal one still shutting down per existing semantics (see the\n * comment below).\n */\nimport { config as loadDotenv } from \"dotenv\";\nimport { serve } from \"@hono/node-server\";\nimport { buildAppDeps, createApp } from \"./app.js\";\nimport { resolveServerConfig } from \"./config.js\";\n\nloadDotenv({ quiet: true });\n\nconst config = resolveServerConfig();\nconst deps = buildAppDeps(config);\nconst app = createApp(deps);\n\n// Built-in admin seed (idempotent): creates admin (initial password admin123) and adopts default_project when the users table is empty.\nawait deps.authService.seedAdmin();\n\n// Schedule scheduler: startup reconciliation (missed, don't backfill) + periodic scan; only active while the server is running.\nawait deps.scheduler.start();\n\nconst server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {\n console.log(`penguin-server 已启动: http://${config.host}:${info.port}`);\n console.log(`数据根目录: ${config.root}`);\n console.log(`SQLite: ${config.dbPath}`);\n});\n\nlet shuttingDown = false;\nasync function shutdown(signal: string, exitCode = 0): Promise<void> {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`收到 ${signal},正在关停…`);\n deps.scheduler.stop();\n await deps.manager.shutdown(5000);\n deps.channels.dispose();\n server.close(() => {\n deps.db.close();\n process.exit(exitCode);\n });\n // Fallback: a long-lived SSE connection may block the close callback, so force exit after 1s.\n setTimeout(() => process.exit(exitCode), 1000).unref();\n}\n\nprocess.on(\"SIGINT\", () => void shutdown(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => void shutdown(\"SIGTERM\"));\n\n// Process-level error fallback: once a background\n// fire-and-forget promise (title generation, Session drive, etc.) throws, the error\n// reaches the process without passing through any catch — persist it first for a\n// record, then handle each case according to its nature.\nprocess.on(\"uncaughtException\", (err) => {\n console.error(`[server] 未捕获异常: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"uncaught_exception\" });\n // From this point the process state can't be trusted (the error was never converged\n // by any catch): don't swallow it — wrap up per existing shutdown semantics and exit\n // with a nonzero code (equivalent to Node's default crash exit, just with an extra\n // persist and graceful wrap-up).\n // Must exit even if shutdown itself errors — never let \"caught a fatal error\" turn\n // into \"the process limps along in a broken state\".\n void shutdown(\"uncaughtException\", 1).catch(() => process.exit(1));\n});\nprocess.on(\"unhandledRejection\", (reason) => {\n const err = reason instanceof Error ? reason : new Error(String(reason));\n console.error(`[server] 未处理的 Promise 拒绝: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"unhandled_rejection\" });\n // Unlike uncaughtException, this **doesn't** exit: a rejected promise is a localized\n // failure of some background task, and the process state isn't compromised; dragging\n // down the entire service for it (Node's default behavior) isn't worth it — persist +\n // log, then keep serving.\n});\n","/**\n * Hono app assembly: middleware + route mounting + static hosting +\n * error handling.\n *\n * `createApp(deps)` is pure assembly (does not listen on a port): tests inject requests via\n * `app.request()`; `buildAppDeps(config)` assembles all services from config (test doubles\n * like SessionLoader can be injected). The startup entry point is in index.ts.\n */\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ServerConfig } from \"./config.js\";\nimport { openDatabase } from \"./db/database.js\";\nimport { AgentsRepo } from \"./db/repos/agents.js\";\nimport { AuthSessionsRepo } from \"./db/repos/auth-sessions.js\";\nimport { ErrorsRepo } from \"./db/repos/errors.js\";\nimport { MembersRepo } from \"./db/repos/members.js\";\nimport { ProjectsRepo } from \"./db/repos/projects.js\";\nimport { SchedulesRepo } from \"./db/repos/schedules.js\";\nimport { SessionsRepo } from \"./db/repos/sessions.js\";\nimport { UiPrefsRepo } from \"./db/repos/ui-prefs.js\";\nimport { UsageRepo } from \"./db/repos/usage.js\";\nimport { UsersRepo } from \"./db/repos/users.js\";\nimport type { UserRow } from \"./db/repos/users.js\";\nimport { authMiddleware, jsonOnlyWrites } from \"./auth/middleware.js\";\nimport type { AppEnv } from \"./auth/middleware.js\";\nimport { AuthService } from \"./auth/service.js\";\nimport { handleError, HttpError, errorBody } from \"./http/errors.js\";\nimport { adminUsersRoutes } from \"./http/routes/admin.js\";\nimport { authRoutes } from \"./http/routes/auth.js\";\nimport { meRoutes } from \"./http/routes/me.js\";\nimport { eventsRoutes, userChannelKey } from \"./http/routes/events.js\";\nimport { projectsRoutes } from \"./http/routes/projects.js\";\nimport { membersRoutes } from \"./http/routes/members.js\";\nimport { modelsRoutes } from \"./http/routes/models.js\";\nimport { vaultRoutes } from \"./http/routes/vault.js\";\nimport { scheduleRoutes } from \"./http/routes/schedules.js\";\nimport { benchmarksRoutes } from \"./http/routes/benchmarks.js\";\nimport { agentSkillsRoutes, skillLibraryRoutes } from \"./http/routes/skills.js\";\nimport { agentTransferRoutes } from \"./http/routes/agent-transfer.js\";\nimport { agentsRoutes } from \"./http/routes/agents.js\";\nimport { dirsRoutes } from \"./http/routes/dirs.js\";\nimport { agentConfigRoutes } from \"./http/routes/agent-config.js\";\nimport { agentTracesRoutes } from \"./http/routes/agent-traces.js\";\nimport { usageRoutes } from \"./http/routes/usage.js\";\nimport { agentSessionsRoutes, sessionsRoutes } from \"./http/routes/sessions.js\";\nimport { ChannelHub } from \"./runtime/channel.js\";\nimport { ErrorRecorder } from \"./runtime/error-recorder.js\";\nimport { createCoreSessionLoader, SessionManager } from \"./runtime/session-manager.js\";\nimport type { SessionLoader } from \"./runtime/session-manager.js\";\nimport { Scheduler } from \"./runtime/scheduler.js\";\nimport { TitleGenerator } from \"./runtime/title-generator.js\";\nimport type { TitleNotifier } from \"./runtime/title-generator.js\";\nimport { UsageRecorder } from \"./runtime/usage-recorder.js\";\nimport { AdminService } from \"./services/admin-service.js\";\nimport { AgentConfigService } from \"./services/agent-config-service.js\";\nimport { AgentService } from \"./services/agent-service.js\";\nimport { BenchmarkService } from \"./services/benchmark-service.js\";\nimport { SnapshotService } from \"./services/snapshot-service.js\";\nimport { ProjectConfigService } from \"./services/project-config-service.js\";\nimport { ProjectService } from \"./services/project-service.js\";\nimport { SessionService } from \"./services/session-service.js\";\nimport { TraceService } from \"./services/trace-service.js\";\nimport { UsageService } from \"./services/usage-service.js\";\nimport { WorkspaceFilesService } from \"./services/workspace-files-service.js\";\n\n/** Request body size limit (tasks may carry data: images): 20MB. */\nconst MAX_BODY_BYTES = 20 * 1024 * 1024;\n\nexport interface AppDeps {\n config: ServerConfig;\n db: DatabaseSync;\n sessionsRepo: SessionsRepo;\n prefsRepo: UiPrefsRepo;\n authService: AuthService;\n adminService: AdminService;\n projectService: ProjectService;\n projectConfigService: ProjectConfigService;\n agentService: AgentService;\n agentConfigService: AgentConfigService;\n sessionService: SessionService;\n traceService: TraceService;\n usageService: UsageService;\n workspaceFiles: WorkspaceFilesService;\n benchmarks: BenchmarkService;\n snapshots: SnapshotService;\n schedulesRepo: SchedulesRepo;\n scheduler: Scheduler;\n channels: ChannelHub;\n manager: SessionManager;\n /** Error persistence (shared by app.onError and various background capture points; the process-level fallback is in index.ts). */\n errors: ErrorRecorder;\n /** Request log output (minimal one-liner); tests inject a noop. */\n log: (line: string) => void;\n}\n\nexport interface BuildDepsOverrides {\n /** Test double: session-manager's underlying loader (avoids the real LLM/SDK path). */\n loader?: SessionLoader;\n /** Test double: Session title generator (avoids real LLM requests). */\n titles?: TitleNotifier;\n log?: (line: string) => void;\n now?: () => Date;\n}\n\n/** Assemble all services from config (shared by production and tests; tests pass dbPath=\":memory:\" and a temp root). */\nexport function buildAppDeps(config: ServerConfig, overrides: BuildDepsOverrides = {}): AppDeps {\n const db = openDatabase(config.dbPath);\n const log = overrides.log ?? ((line: string) => console.log(line));\n\n const usersRepo = new UsersRepo(db);\n const authSessionsRepo = new AuthSessionsRepo(db);\n const projectsRepo = new ProjectsRepo(db);\n const membersRepo = new MembersRepo(db);\n const agentsRepo = new AgentsRepo(db);\n const sessionsRepo = new SessionsRepo(db);\n const usageRepo = new UsageRepo(db);\n const errorsRepo = new ErrorsRepo(db);\n const prefsRepo = new UiPrefsRepo(db);\n const schedulesRepo = new SchedulesRepo(db);\n\n const projectConfigService = new ProjectConfigService(config.root);\n const agentConfigService = new AgentConfigService(config.root);\n const agentService = new AgentService(config.root, agentsRepo, agentConfigService);\n const traceService = new TraceService(config.root);\n const workspaceFiles = new WorkspaceFilesService();\n const benchmarks = new BenchmarkService(config.root);\n const snapshots = new SnapshotService(config.root);\n const usageService = new UsageService(\n usageRepo,\n errorsRepo,\n (projectId, provider, modelId) => projectConfigService.getPricing(projectId, provider, modelId),\n overrides.now ?? (() => new Date()),\n );\n\n // Channel idle reclamation skips active Sessions (running/compacting can go a long time\n // without a publish, e.g. while waiting for approval).\n // manager is created after channels: use a lazy predicate (managerRef is assigned by the\n // time the sweep timer fires).\n let managerRef: SessionManager | undefined;\n const channels = new ChannelHub({\n isActive: (key) => managerRef !== undefined && managerRef.statusOf(key) !== \"idle\",\n });\n const recorder = new UsageRecorder(usageRepo, overrides.now ?? (() => new Date()));\n const errors = new ErrorRecorder(errorsRepo, overrides.now ?? (() => new Date()));\n const titles =\n overrides.titles ??\n new TitleGenerator({ sessions: sessionsRepo, channels, recorder, errors, log });\n const manager = new SessionManager({\n sessions: sessionsRepo,\n channels,\n loader: overrides.loader ?? createCoreSessionLoader(config.root),\n recorder,\n errors,\n titles,\n log,\n });\n managerRef = manager;\n\n const projectService = new ProjectService({\n root: config.root,\n users: usersRepo,\n projects: projectsRepo,\n members: membersRepo,\n agents: agentsRepo,\n sessions: sessionsRepo,\n usage: usageRepo,\n errors: errorsRepo,\n schedules: schedulesRepo,\n projectConfig: projectConfigService,\n manager,\n });\n const authService = new AuthService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n provisionInitialProject: (user, isAdmin) =>\n projectService.provisionInitialProject(user, isAdmin),\n sessionTtlMs: config.authSessionTtlMs,\n sessionRenewMs: config.authSessionRenewMs,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const adminService = new AdminService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n projects: projectsRepo,\n projectService,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const sessionService = new SessionService({\n root: config.root,\n sessions: sessionsRepo,\n manager,\n projectConfig: projectConfigService,\n });\n // Schedule scheduler: active only while the server is running. Only\n // assembled here; start() is called in index.ts (tests drive it via tickOnce, no real timer).\n const scheduler = new Scheduler({\n root: config.root,\n repo: schedulesRepo,\n projects: projectsRepo,\n sessions: sessionsRepo,\n runner: manager,\n sessionCreator: sessionService,\n errors,\n notify: (userId, event) => {\n channels.get(userChannelKey(userId)).publish(event, \"server_event\");\n },\n ...(overrides.now ? { now: () => overrides.now!().getTime() } : {}),\n });\n\n return {\n config,\n db,\n sessionsRepo,\n prefsRepo,\n authService,\n adminService,\n projectService,\n projectConfigService,\n agentService,\n agentConfigService,\n sessionService,\n traceService,\n usageService,\n workspaceFiles,\n benchmarks,\n snapshots,\n schedulesRepo,\n scheduler,\n channels,\n manager,\n errors,\n log,\n };\n}\n\n/** Assembles the Hono app (does not listen on a port). */\nexport function createApp(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // Error recording is layered in a lambda wrapping onError: handleError stays a\n // pure function with unchanged behavior (HttpError is mapped as-is, unknown\n // exceptions are logged with a stack trace and collapsed to 500), and recording\n // to the DB is just a side-effect layered on top.\n app.onError((err, c) => {\n const projectId = attributedProjectId(c, deps);\n deps.errors.record({\n source: \"http\",\n err,\n ...(projectId !== undefined ? { ctx: { projectId } } : {}),\n });\n return handleError(err, c);\n });\n app.notFound((c) => c.json(errorBody(\"not_found\", \"接口不存在。\"), 404));\n\n // Request logging: a minimal one-liner (method path status ms).\n app.use(\"*\", async (c, next) => {\n const start = performance.now();\n await next();\n const ms = Math.round(performance.now() - start);\n deps.log(`${c.req.method} ${c.req.path} ${c.res.status} ${ms}ms`);\n });\n\n // API common defenses: request body size cap (20MB) and write-request Content-Type (one of the CSRF MVP defenses).\n app.use(\"/api/*\", async (c, next) => {\n const contentLength = Number(c.req.header(\"content-length\") ?? 0);\n if (contentLength > MAX_BODY_BYTES) {\n throw new HttpError(413, \"payload_too_large\", \"请求体超过 20MB 上限。\");\n }\n await next();\n });\n app.use(\"/api/*\", jsonOnlyWrites);\n\n // Public routes (no login required).\n app.route(\"/api/auth\", authRoutes(deps));\n\n // Protected routes: cookie -> auth_session -> user.\n const auth = authMiddleware(deps.authService);\n app.use(\"/api/*\", auth);\n app.route(\"/api/me\", meRoutes(deps));\n app.route(\"/api/admin/users\", adminUsersRoutes(deps));\n app.route(\"/api/events\", eventsRoutes(deps));\n // Skill library listing: readable once logged in, not nested under a Project prefix.\n app.route(\"/api/skills\", skillLibraryRoutes());\n app.route(\"/api/projects\", projectsRoutes(deps));\n app.route(\"/api/projects/:projectId/members\", membersRoutes(deps));\n app.route(\"/api/projects/:projectId/models\", modelsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents\", agentsRoutes(deps));\n app.route(\"/api/projects/:projectId/dirs\", dirsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/config\", agentConfigRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/vault\", vaultRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/schedules\", scheduleRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/benchmarks\", benchmarksRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/skills\", agentSkillsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId\", agentTransferRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/traces\", agentTracesRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/sessions\", agentSessionsRoutes(deps));\n app.route(\"/api/projects/:projectId/usage\", usageRoutes(deps));\n app.route(\"/api/sessions\", sessionsRoutes(deps));\n\n // Static hosting (production): serves the frontend build output when webDist exists, with SPA fallback to index.html.\n if (fs.existsSync(deps.config.webDist)) {\n registerStaticRoutes(app, deps.config.webDist);\n }\n\n return app;\n}\n\n/**\n * The Project an error is attributed to: only\n * attributed when the URL has a `:projectId` **and** the requester genuinely has\n * access to that Project; otherwise recorded as unattributed (`project_id IS\n * NULL`, visible only to admins).\n *\n * onError also has to handle requests that **haven't passed permission checks\n * yet** — a 401 from being logged out, a 404 from not being a member, both get\n * recorded here. Attributing directly from the URL parameter would let anyone\n * (not necessarily a member of that Project, or even logged in) pick a projectId\n * and hammer it repeatedly to pollute another user's Project with error stats.\n * Traces that can't be attributed simply fall into the admin view (unattributed\n * errors are only visible to admins by design anyway), which is exactly where\n * unauthorized probing belongs.\n *\n * Two defenses here, because this code runs on the error-handling path:\n * - `c.var.user`'s static type is non-null, but authMiddleware never sets it\n * **before** throwing the 401 when logged out, so at runtime it may actually be\n * undefined — it can only be read safely, never destructured directly.\n * - Exceptions are swallowed entirely: throwing here would break onError itself\n * (possibly recursively); any judgment failure falls back to unattributed.\n */\nfunction attributedProjectId(c: Context<AppEnv>, deps: AppDeps): string | undefined {\n try {\n const projectId = c.req.param(\"projectId\");\n if (projectId === undefined) return undefined;\n const user = c.get(\"user\") as UserRow | undefined;\n if (user === undefined) return undefined;\n return deps.projectService.canAccess(user.userId, projectId) ? projectId : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".ico\": \"image/x-icon\",\n \".map\": \"application/json\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".woff2\": \"font/woff2\",\n};\n\n/** Minimal static file server (avoiding an extra dependency): path traversal protection + SPA fallback. */\nfunction registerStaticRoutes(app: Hono<AppEnv>, webDist: string): void {\n app.get(\"*\", async (c) => {\n const reqPath = decodeURIComponent(c.req.path);\n if (reqPath.startsWith(\"/api/\")) {\n return c.json(errorBody(\"not_found\", \"接口不存在。\"), 404);\n }\n const rel = reqPath.replace(/^\\/+/, \"\");\n const resolved = path.resolve(webDist, rel === \"\" ? \"index.html\" : rel);\n // Guard against path traversal: once resolved, it must still be inside webDist.\n const base = path.resolve(webDist);\n const target =\n resolved === base || resolved.startsWith(base + path.sep)\n ? resolved\n : path.join(base, \"index.html\");\n let file = target;\n try {\n const stat = await fsp.stat(file);\n if (stat.isDirectory()) file = path.join(file, \"index.html\");\n await fsp.access(file);\n } catch {\n file = path.join(base, \"index.html\"); // SPA fallback\n }\n let content: Buffer;\n try {\n content = await fsp.readFile(file);\n } catch {\n return c.json(errorBody(\"not_found\", \"资源不存在。\"), 404);\n }\n const type = CONTENT_TYPES[path.extname(file).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(content), {\n status: 200,\n headers: { \"Content-Type\": type },\n });\n });\n}\n","/**\n * SQLite connection & initialization (node:sqlite DatabaseSync).\n *\n * Single process, single writer: a synchronous API is sufficient and avoids a connection\n * pool; WAL mode and foreign key constraints are enabled. Table-creation SQL runs on open\n * (idempotent), with no migration branches (product not yet released).\n */\nimport { mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { SCHEMA_SQL } from \"./schema.js\";\n\n// Fetch the runtime module via process.getBuiltinModule (node >=22.3): avoids static\n// resolution of `node:sqlite` by bundlers/vite (some tools' builtin lists don't yet\n// recognize this experimental module).\nconst sqlite = process.getBuiltinModule(\"node:sqlite\");\n\n/** Open (creating if necessary) the database: ensure the parent directory exists, set PRAGMAs, run table creation. */\nexport function openDatabase(dbPath: string): DatabaseSync {\n if (dbPath !== \":memory:\") {\n mkdirSync(path.dirname(dbPath), { recursive: true });\n }\n const db = new sqlite.DatabaseSync(dbPath);\n db.exec(\"PRAGMA journal_mode = WAL;\");\n db.exec(\"PRAGMA foreign_keys = ON;\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n","/**\n * SQLite table-creation SQL.\n *\n * SQLite stores only indexes and aggregates: users / login sessions / Project authorization /\n * Agent & Session indexes / usage summaries / error records / UI preferences. Agent State,\n * Trace, and Workspace still follow the local directory-based storage rules.\n * Product not yet released: no migration branches — everything is CREATE IF NOT EXISTS, formed once.\n */\n\nexport const SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS users (\n user_id TEXT PRIMARY KEY, -- 语义 id 即登录名:^[a-z][a-z0-9_-]{1,31}$\n password_hash TEXT NOT NULL, -- scrypt$N$r$p$salt$hash(base64)\n is_admin INTEGER NOT NULL DEFAULT 0, -- 内置 admin(启动时种子)为 1\n password_is_initial INTEGER NOT NULL DEFAULT 0, -- 1=初始密码(种子/管理员设置);本人改密后清 0\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS auth_sessions (\n token_hash TEXT PRIMARY KEY, -- sha256(token) hex;cookie 只存原始 token\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL -- 7 天滑动续期(剩余 <6 天则续满)\n);\nCREATE TABLE IF NOT EXISTS projects (\n project_id TEXT PRIMARY KEY, -- 目录名即 id;显示名在 project_config.toml\n owner_user_id TEXT NOT NULL REFERENCES users(user_id),\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members ( -- 仅 member 授权关系;owner 不入此表\n project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, user_id)\n);\nCREATE TABLE IF NOT EXISTS agents ( -- 索引;name/description 在 system_config.yaml\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, agent_id)\n);\nCREATE TABLE IF NOT EXISTS sessions (\n session_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n provider TEXT NOT NULL, -- 会话模型的厂商分组(与 model_id 成对构成模型引用)\n model_id TEXT NOT NULL, -- 上游模型 id(原样发给 AgentHub;禁止 <provider>/<id> 拼接)\n workspace TEXT NOT NULL,\n approval_mode TEXT NOT NULL DEFAULT 'allow-all', -- allow-all|deny-all|read-only|always-ask\n title TEXT, -- 首次对话后由模型自动生成;NULL=未生成(前端显示「新对话」)\n archived_at TEXT, -- 归档时刻;NULL=未归档(默认展示;归档后收进「已归档」)\n source TEXT, -- 会话来源:NULL=用户创建 | schedule(定时任务)| subagent(子会话)\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS usage_records (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- 本地日期 yyyy-mm-dd(聚合键,与 Trace 日期目录同口径)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n session_id TEXT NOT NULL, -- 顶层 Session(子会话消耗计入所属主 Session)\n origin_session_id TEXT, -- 直接来源子 Session(origin 链末项);NULL=主会话\n provider TEXT NOT NULL, -- 厂商分组:与 model_id 成对构成归因键,聚合一律 GROUP BY provider, model_id\n model_id TEXT NOT NULL, -- 上游模型 id(与 provider 成对;同名 model_id 跨厂商分开聚合)\n cache_read INTEGER NOT NULL,\n cache_write INTEGER NOT NULL,\n output INTEGER NOT NULL,\n total INTEGER NOT NULL, -- 取 token_usage.request(每 Request 一条)\n status TEXT NOT NULL DEFAULT 'completed' -- 请求结局:completed=成功(含 token);其余=失败(0 token,供成功率)\n); -- 成本不落库:查询时按当前 pricing 实时折算\nCREATE INDEX IF NOT EXISTS idx_usage_project_date ON usage_records(project_id, date);\nCREATE INDEX IF NOT EXISTS idx_usage_session ON usage_records(session_id);\nCREATE TABLE IF NOT EXISTS error_records ( -- 服务端异常捕获(统计中心「异常」)\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- 本地日期 yyyy-mm-dd(与 usage_records 同口径)\n project_id TEXT, -- 可空:登录/注册、进程级异常没有 Project 上下文\n agent_id TEXT,\n session_id TEXT,\n source TEXT NOT NULL, -- http | session | usage | title | subagent | process | llm | environment | schedule\n kind TEXT NOT NULL, -- expected(HttpError,业务 4xx)| unexpected(500/运行时)\n code TEXT NOT NULL, -- HttpError.code / internal / session_run_failed / ...\n status INTEGER, -- HTTP 状态码;非 HTTP 来源为 NULL\n message TEXT NOT NULL -- 截断 500 字符(不存堆栈:堆栈只进日志)\n);\nCREATE INDEX IF NOT EXISTS idx_error_project_date ON error_records(project_id, date);\nCREATE TABLE IF NOT EXISTS schedule_state ( -- 定时任务运行状态(文件是声明式意图,系统不写回)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n name TEXT NOT NULL, -- 文件名(去 .toml)即标识\n creator_user_id TEXT, -- 创建者(API 创建时记;手编文件对账登记回退 Project owner)\n start_at_ms INTEGER NOT NULL, -- 定义身份:start_at 变更视为新任务实例,重置触发状态\n def_hash TEXT NOT NULL, -- 文件内容指纹:变更即清除失效标记(文件修改后重新生效)\n last_slot_ms INTEGER, -- 已消化的最近应触发时刻(触发或跳过都推进;重启不重复触发)\n last_fired_at TEXT, -- 最近实际发送时刻(展示用)\n fired_once INTEGER NOT NULL DEFAULT 0, -- 一次性任务已触发\n missed INTEGER NOT NULL DEFAULT 0, -- 一次性任务已错过(启动/登记对账标记,错过不补)\n invalid_reason TEXT, -- 失效原因(如绑定 Session 已删除);NULL 即正常\n PRIMARY KEY (project_id, agent_id, name)\n);\nCREATE TABLE IF NOT EXISTS ui_prefs (\n user_id TEXT PRIMARY KEY REFERENCES users(user_id) ON DELETE CASCADE,\n prefs_json TEXT NOT NULL -- {theme?, lastProjectId?, ...} 自由 JSON\n);\n`;\n","/**\n * agents table repo: Agent index; name/description live in system_config.yaml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AgentRow {\n projectId: string;\n agentId: string;\n createdAt: string;\n}\n\nexport class AgentsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Idempotent insert: backfills an untracked Agent discovered via directory scan; shared with explicit creation. */\n insertOrIgnore(row: AgentRow): void {\n this.db\n .prepare(\"INSERT OR IGNORE INTO agents (project_id, agent_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.agentId, row.createdAt);\n }\n\n exists(projectId: string, agentId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM agents WHERE project_id = ? AND agent_id = ?\")\n .get(projectId, agentId);\n return r !== undefined;\n }\n\n list(projectId: string): AgentRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM agents WHERE project_id = ? ORDER BY created_at ASC, agent_id ASC\")\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM agents WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM agents WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * auth_sessions table repo (server-side sessions backing the HttpOnly cookie).\n *\n * Stores only the sha256(token) hex hash; the raw token appears only in the cookie.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AuthSessionRow {\n tokenHash: string;\n userId: string;\n createdAt: string;\n expiresAt: string;\n}\n\nexport class AuthSessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: AuthSessionRow): void {\n this.db\n .prepare(\n \"INSERT INTO auth_sessions (token_hash, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)\",\n )\n .run(row.tokenHash, row.userId, row.createdAt, row.expiresAt);\n }\n\n findByTokenHash(tokenHash: string): AuthSessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM auth_sessions WHERE token_hash = ?\").get(tokenHash);\n if (!r) return null;\n return {\n tokenHash: r.token_hash as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n expiresAt: r.expires_at as string,\n };\n }\n\n /** Sliding renewal: update the expiration time. */\n touch(tokenHash: string, expiresAt: string): void {\n this.db\n .prepare(\"UPDATE auth_sessions SET expires_at = ? WHERE token_hash = ?\")\n .run(expiresAt, tokenHash);\n }\n\n delete(tokenHash: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE token_hash = ?\").run(tokenHash);\n }\n\n /** Opportunistically clean up expired sessions (called during login/validation). */\n deleteExpired(nowIso: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE expires_at < ?\").run(nowIso);\n }\n\n /** Clear all sessions for a user (forces re-login after an admin resets the password). */\n deleteByUser(userId: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * error_records table repo: one row per error the server catches.\n *\n * Key difference from usage_records: **all attribution columns are nullable** — errors\n * from the login/register endpoints have no Project, and process-level catch-alls\n * (uncaughtException) don't even have a request. These **unattributed errors are visible\n * only to admins** (`ErrorFilter.includeGlobal`, defaults to false): they represent other\n * users' login failures, misdirected Session access, and process crashes (the message may\n * contain internal paths and variable values), so surfacing them in any regular member's\n * statistics center would be a cross-tenant information leak — regular members see only\n * their own Project's errors. Admins still see the category that most needs visibility.\n *\n * **Row cap** (MAX_ROWS): errors often come in storms (an API scan producing a wall of\n * 404s, a tool failing repeatedly in a loop), and an uncapped table would blow up disk\n * usage. So the insert path enforces the cap: check capacity every PRUNE_EVERY inserts\n * (insertion sits on the error-handling path, so we avoid COUNT on every call), and when\n * over the limit, evict the oldest rows in ascending id order. The excess is computed via\n * an **exact COUNT**, not an approximation like `id <= MAX(id) - :max` — after\n * deleteByProject removes rows, id and row count diverge, and the approximation would\n * wrongly delete still-valid data within the cap. The first line of defense is\n * ErrorRecorder's short-window deduplication.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/** Row cap: once exceeded, oldest rows are evicted in ascending id order (see file header). */\nexport const MAX_ROWS = 20000;\n\n/** Check capacity every N inserts (see file header: insertion sits on the error-handling path, so we avoid COUNT on every call). */\nexport const PRUNE_EVERY = 200;\n\n/** Capacity parameters (default to the two constants above; tests inject small values to exercise the eviction path). */\nexport interface ErrorsRepoLimits {\n maxRows?: number;\n pruneEvery?: number;\n}\n\nexport interface ErrorRecordInsert {\n ts: string;\n date: string;\n projectId: string | null;\n agentId: string | null;\n sessionId: string | null;\n source: string;\n /** expected (HttpError, business 4xx) | unexpected (500 / unforeseen runtime error). */\n kind: string;\n code: string;\n status: number | null;\n message: string;\n}\n\n/** Generic filter: date range + agent (errors have no Model dimension, so no model filter). */\nexport interface ErrorFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Whether to include unattributed errors (`project_id IS NULL`): admins only, defaults to false (see file header). */\n includeGlobal?: boolean;\n}\n\n/** Total error count and how many are unexpected (stats for the statistics center's error panel). */\nexport interface ErrorSummary {\n total: number;\n unexpected: number;\n}\n\n/** Occurrence count for one source · code pair (the error panel's \"most common\" metric). */\nexport interface ErrorCodeCount {\n source: string;\n code: string;\n kind: string;\n count: number;\n}\n\n/** One error summary row (a row in the error panel's table). */\nexport interface ErrorItem {\n ts: string;\n source: string;\n code: string;\n kind: string;\n message: string;\n}\n\nexport class ErrorsRepo {\n private readonly maxRows: number;\n private readonly pruneEvery: number;\n /** Insert count since the last capacity check (see file header: avoids COUNT on every call). */\n private sinceCheck = 0;\n\n constructor(\n private readonly db: DatabaseSync,\n limits: ErrorsRepoLimits = {},\n ) {\n this.maxRows = limits.maxRows ?? MAX_ROWS;\n this.pruneEvery = limits.pruneEvery ?? PRUNE_EVERY;\n }\n\n insert(r: ErrorRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO error_records\n (ts, date, project_id, agent_id, session_id, source, kind, code, status, message)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.source,\n r.kind,\n r.code,\n r.status,\n r.message,\n );\n if (++this.sinceCheck >= this.pruneEvery) {\n this.sinceCheck = 0;\n this.pruneOverflow();\n }\n }\n\n /** Capacity enforcement (see file header): compute the exact excess via COUNT, then delete the oldest rows in ascending id order. */\n private pruneOverflow(): void {\n const row = this.db.prepare(\"SELECT COUNT(*) AS n FROM error_records\").get()!;\n const excess = (row.n as number) - this.maxRows;\n if (excess <= 0) return;\n this.db\n .prepare(\n `DELETE FROM error_records WHERE id IN (\n SELECT id FROM error_records ORDER BY id ASC LIMIT :excess\n )`,\n )\n .run({ excess });\n }\n\n /** WHERE fragment and named params: this Project (admins additionally get unattributed errors), plus optional date/agent filter. */\n private conds(\n projectId: string,\n f: ErrorFilter,\n ): { where: string; params: Record<string, string> } {\n // Unattributed errors (login failures, process crashes, ...) are visible only to admins; otherwise it's a cross-tenant leak — see file header.\n const conds = [\n f.includeGlobal === true ? \"(project_id = :pid OR project_id IS NULL)\" : \"project_id = :pid\",\n ];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n // Filtering by Agent naturally leaves only that Agent's errors (HTTP / process-level errors have no agent_id).\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Total count + how many are unexpected. */\n summary(projectId: string, f: ErrorFilter = {}): ErrorSummary {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS total,\n COALESCE(SUM(CASE WHEN kind = 'unexpected' THEN 1 ELSE 0 END), 0) AS unexpected\n FROM error_records WHERE ${where}`,\n )\n .get(params)!;\n return { total: row.total as number, unexpected: row.unexpected as number };\n }\n\n /** The most frequent source · code (ties broken by code's lexicographic order); null if there are no errors. */\n topCode(projectId: string, f: ErrorFilter = {}): ErrorCodeCount | null {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT source, code, kind, COUNT(*) AS count\n FROM error_records WHERE ${where}\n GROUP BY source, code, kind\n ORDER BY count DESC, code ASC\n LIMIT 1`,\n )\n .get(params);\n if (!row) return null;\n return {\n source: row.source as string,\n code: row.code as string,\n kind: row.kind as string,\n count: row.count as number,\n };\n }\n\n /** The most recent `limit` entries (reverse chronological order). */\n recent(projectId: string, f: ErrorFilter = {}, limit = 20): ErrorItem[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ts, source, code, kind, message\n FROM error_records WHERE ${where}\n ORDER BY id DESC LIMIT :limit`,\n )\n .all({ ...params, limit });\n return rows.map((r) => ({\n ts: r.ts as string,\n source: r.source as string,\n code: r.code as string,\n kind: r.kind as string,\n message: r.message as string,\n }));\n }\n\n /** Cascading cleanup on Project deletion (unattributed errors belong to no Project and are unaffected). */\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM error_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for the project_members table: only member\n * authorization relationships — the owner is never in this table.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface MemberRow {\n projectId: string;\n userId: string;\n createdAt: string;\n}\n\nexport class MembersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: MemberRow): void {\n this.db\n .prepare(\"INSERT INTO project_members (project_id, user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.userId, row.createdAt);\n }\n\n isMember(projectId: string, userId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM project_members WHERE project_id = ? AND user_id = ?\")\n .get(projectId, userId);\n return r !== undefined;\n }\n\n list(projectId: string): MemberRow[] {\n const rows = this.db\n .prepare(\n \"SELECT project_id, user_id, created_at FROM project_members WHERE project_id = ? ORDER BY created_at ASC\",\n )\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, userId: string): void {\n this.db\n .prepare(\"DELETE FROM project_members WHERE project_id = ? AND user_id = ?\")\n .run(projectId, userId);\n }\n}\n","/**\n * Repo for the projects table: an index of ownership\n * relationships; the display name lives in project_config.toml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ProjectRole } from \"../../api/types.js\";\n\nexport interface ProjectRow {\n projectId: string;\n ownerUserId: string;\n createdAt: string;\n}\n\nexport interface AccessibleProjectRow extends ProjectRow {\n role: ProjectRole;\n}\n\nfunction mapRow(r: Record<string, unknown>): ProjectRow {\n return {\n projectId: r.project_id as string,\n ownerUserId: r.owner_user_id as string,\n createdAt: r.created_at as string,\n };\n}\n\nexport class ProjectsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: ProjectRow): void {\n this.db\n .prepare(\"INSERT INTO projects (project_id, owner_user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.ownerUserId, row.createdAt);\n }\n\n findById(projectId: string): ProjectRow | null {\n const r = this.db.prepare(\"SELECT * FROM projects WHERE project_id = ?\").get(projectId);\n return r ? mapRow(r) : null;\n }\n\n /** All Projects (used by the scheduler's reconciliation scan), ascending by creation time. */\n listAll(): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects ORDER BY created_at ASC, project_id ASC\")\n .all();\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /** Projects owned by or shared with the current user (including their role), ascending by creation time. */\n listAccessible(userId: string): AccessibleProjectRow[] {\n const rows = this.db\n .prepare(\n `SELECT p.project_id, p.owner_user_id, p.created_at,\n CASE WHEN p.owner_user_id = :uid THEN 'owner' ELSE 'member' END AS role\n FROM projects p\n WHERE p.owner_user_id = :uid\n OR EXISTS (SELECT 1 FROM project_members m\n WHERE m.project_id = p.project_id AND m.user_id = :uid)\n ORDER BY p.created_at ASC, p.project_id ASC`,\n )\n .all({ uid: userId });\n return rows.map((r) => ({\n ...mapRow(r),\n role: r.role as ProjectRole,\n }));\n }\n\n /** All Projects owned by a given user (used for cascading cleanup when an admin deletes a user). */\n listByOwner(userId: string): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects WHERE owner_user_id = ? ORDER BY created_at ASC\")\n .all(userId);\n return rows.map(mapRow);\n }\n\n delete(projectId: string): void {\n this.db.prepare(\"DELETE FROM projects WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for schedule runtime state: intent and state are separate — the file is\n * declarative intent, this table only records runtime state such as\n * \"fired before / last fired / missed / disabled\" plus the creator.\n *\n * Identity rule: a change to `start_at` is treated as a new task instance\n * (registerOrSync resets the trigger state); a change to the file content fingerprint\n * only clears the disabled flag (the file becomes effective again after reconciliation).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface ScheduleStateRow {\n projectId: string;\n agentId: string;\n name: string;\n creatorUserId: string | null;\n startAtMs: number;\n defHash: string;\n lastSlotMs: number | null;\n lastFiredAt: string | null;\n firedOnce: boolean;\n missed: boolean;\n invalidReason: string | null;\n}\n\nfunction mapRow(r: Record<string, unknown>): ScheduleStateRow {\n return {\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n name: r.name as string,\n creatorUserId: (r.creator_user_id as string | null) ?? null,\n startAtMs: Number(r.start_at_ms),\n defHash: r.def_hash as string,\n lastSlotMs: r.last_slot_ms === null ? null : Number(r.last_slot_ms),\n lastFiredAt: (r.last_fired_at as string | null) ?? null,\n firedOnce: Number(r.fired_once) === 1,\n missed: Number(r.missed) === 1,\n invalidReason: (r.invalid_reason as string | null) ?? null,\n };\n}\n\nexport class SchedulesRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n find(projectId: string, agentId: string, name: string): ScheduleStateRow | null {\n const r = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .get(projectId, agentId, name);\n return r ? mapRow(r as Record<string, unknown>) : null;\n }\n\n listByAgent(projectId: string, agentId: string): ScheduleStateRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? ORDER BY name\")\n .all(projectId, agentId);\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /**\n * Register or sync a task's runtime state, returning the synced row plus a `fresh`\n * flag:\n * - Insert if it doesn't exist (creator is only persisted at this point; a hand-edited\n * file gets registered via reconciliation, with creator falling back to the Project\n * owner);\n * - A change to `start_at` resets the trigger state (a new task instance);\n * - Otherwise, a change to the file fingerprint only clears the disabled flag.\n * `fresh` = this call was an insert or reset — the scheduler only establishes its\n * \"missed, don't backfill\" baseline at this moment; afterward, last_slot being NULL\n * only means \"no scheduled time has been consumed yet\", and it must fire normally once\n * reached.\n */\n registerOrSync(args: {\n projectId: string;\n agentId: string;\n name: string;\n startAtMs: number;\n defHash: string;\n creatorUserId: string | null;\n }): { row: ScheduleStateRow; fresh: boolean } {\n const existing = this.find(args.projectId, args.agentId, args.name);\n let fresh = false;\n if (!existing) {\n this.db\n .prepare(\n `INSERT INTO schedule_state\n (project_id, agent_id, name, creator_user_id, start_at_ms, def_hash)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n .run(\n args.projectId,\n args.agentId,\n args.name,\n args.creatorUserId,\n args.startAtMs,\n args.defHash,\n );\n fresh = true;\n } else if (existing.startAtMs !== args.startAtMs) {\n this.db\n .prepare(\n `UPDATE schedule_state\n SET start_at_ms = ?, def_hash = ?, last_slot_ms = NULL, last_fired_at = NULL,\n fired_once = 0, missed = 0, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.startAtMs, args.defHash, args.projectId, args.agentId, args.name);\n fresh = true;\n } else if (existing.defHash !== args.defHash) {\n this.db\n .prepare(\n `UPDATE schedule_state SET def_hash = ?, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.defHash, args.projectId, args.agentId, args.name);\n }\n const row = this.find(args.projectId, args.agentId, args.name);\n if (!row) throw new Error(\"schedule_state 登记后读取失败\");\n return { row, fresh };\n }\n\n /** Advance the consumed scheduled time (advances whether triggered or skipped; restarts don't re-trigger). */\n markSlot(projectId: string, agentId: string, name: string, slotMs: number): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_slot_ms = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(slotMs, projectId, agentId, name);\n }\n\n /** Record an actual send (also sets fired_once for a one-shot task). */\n markFired(\n projectId: string,\n agentId: string,\n name: string,\n firedAt: string,\n oneShot: boolean,\n ): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_fired_at = ?, fired_once = CASE WHEN ? THEN 1 ELSE fired_once END\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(firedAt, oneShot ? 1 : 0, projectId, agentId, name);\n }\n\n /** Missed marker for a one-shot task (the scheduled time had already passed at startup/registration reconciliation; missed means not backfilled). */\n markMissed(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET missed = 1\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(projectId, agentId, name);\n }\n\n /** Mark as disabled (e.g. the bound Session was deleted); cleared via registerOrSync after the file is modified. */\n markInvalid(projectId: string, agentId: string, name: string, reason: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET invalid_reason = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(reason, projectId, agentId, name);\n }\n\n /** Deleting the file removes the task: clears its runtime state. */\n delete(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\"DELETE FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .run(projectId, agentId, name);\n }\n\n /** Reconciliation cleanup: deletes state rows under this Agent that aren't in the current file list, returning the removed names. */\n deleteMissing(projectId: string, agentId: string, presentNames: string[]): string[] {\n const rows = this.listByAgent(projectId, agentId);\n const present = new Set(presentNames);\n const removed: string[] = [];\n for (const row of rows) {\n if (!present.has(row.name)) {\n this.delete(projectId, agentId, row.name);\n removed.push(row.name);\n }\n }\n return removed;\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM schedule_state WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * sessions table repo:\n * Session index, approval mode, and auto-generated title; Session-level routes use this to look up project ownership.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ApprovalMode } from \"../../api/types.js\";\n\nexport interface SessionRow {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Provider group of the session's model (pairs with `modelId` to form the model reference). */\n provider: string;\n /** Upstream model_id of the session's model (sent as-is to AgentHub; never concatenated). */\n modelId: string;\n workspace: string;\n approvalMode: ApprovalMode;\n /** Auto-generated session title; NULL = not yet generated (frontend shows \"New Conversation\"). */\n title: string | null;\n /** Archive timestamp, ISO; NULL = not archived (omitting on insert defaults to NULL). */\n archivedAt?: string | null;\n /** Session origin: NULL = user-created; schedule = triggered by a Schedule; subagent = registered as a subagent session. */\n source?: \"schedule\" | \"subagent\" | null;\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): SessionRow {\n return {\n sessionId: r.session_id as string,\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n provider: r.provider as string,\n modelId: r.model_id as string,\n workspace: r.workspace as string,\n approvalMode: r.approval_mode as ApprovalMode,\n title: (r.title as string | null) ?? null,\n archivedAt: (r.archived_at as string | null) ?? null,\n source: (r.source as \"schedule\" | \"subagent\" | null) ?? null,\n createdAt: r.created_at as string,\n };\n}\n\nexport class SessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, source, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.source ?? null,\n row.createdAt,\n );\n }\n\n /** Idempotent insert: used when Trace directory discovery backfills a row (concurrent listing discovering the same Session no longer triggers a UNIQUE violation). */\n insertOrIgnore(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT OR IGNORE INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, source, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.source ?? null,\n row.createdAt,\n );\n }\n\n findById(sessionId: string): SessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM sessions WHERE session_id = ?\").get(sessionId);\n return r ? mapRow(r) : null;\n }\n\n listByAgent(projectId: string, agentId: string): SessionRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .all(projectId, agentId);\n return rows.map(mapRow);\n }\n\n listByProject(projectId: string): SessionRow[] {\n const rows = this.db.prepare(\"SELECT * FROM sessions WHERE project_id = ?\").all(projectId);\n return rows.map(mapRow);\n }\n\n updateApprovalMode(sessionId: string, mode: ApprovalMode): void {\n this.db\n .prepare(\"UPDATE sessions SET approval_mode = ? WHERE session_id = ?\")\n .run(mode, sessionId);\n }\n\n updateTitle(sessionId: string, title: string): void {\n this.db.prepare(\"UPDATE sessions SET title = ? WHERE session_id = ?\").run(title, sessionId);\n }\n\n /**\n * Writes only if the title is still NULL. Subagent-session registration and Trace\n * directory discovery backfill can race on the same row, and both are insert-only:\n * whichever inserts first determines the title. Discovery backfill can only supply NULL,\n * so this method fills the title back in without overwriting an existing one (including\n * a user rename or an already-generated title).\n */\n updateTitleIfNull(sessionId: string, title: string): void {\n this.db\n .prepare(\"UPDATE sessions SET title = ? WHERE session_id = ? AND title IS NULL\")\n .run(title, sessionId);\n }\n\n /** Archive / unarchive (archivedAt = ISO or NULL). */\n setArchived(sessionId: string, archivedAt: string | null): void {\n this.db\n .prepare(\"UPDATE sessions SET archived_at = ? WHERE session_id = ?\")\n .run(archivedAt, sessionId);\n }\n\n /** Self-healing: after rebuilding a broken Session with no Trace, update the primary key to the new id. */\n replaceId(oldSessionId: string, newSessionId: string): void {\n this.db\n .prepare(\"UPDATE sessions SET session_id = ? WHERE session_id = ?\")\n .run(newSessionId, oldSessionId);\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE project_id = ?\").run(projectId);\n }\n\n deleteById(sessionId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE session_id = ?\").run(sessionId);\n }\n}\n","/**\n * ui_prefs table repo (UI preferences): free-form JSON storage.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport class UiPrefsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Returns the raw JSON string; null if never set. */\n get(userId: string): string | null {\n const r = this.db.prepare(\"SELECT prefs_json FROM ui_prefs WHERE user_id = ?\").get(userId);\n return r ? (r.prefs_json as string) : null;\n }\n\n set(userId: string, prefsJson: string): void {\n this.db\n .prepare(\n `INSERT INTO ui_prefs (user_id, prefs_json) VALUES (?, ?)\n ON CONFLICT(user_id) DO UPDATE SET prefs_json = excluded.prefs_json`,\n )\n .run(userId, prefsJson);\n }\n}\n","/**\n * usage_records table repo:\n * one row per token_usage (per-request bucket). Stores Token counts only, not cost —\n * cost is computed on the fly by usage-service against current pricing at query time,\n * so every aggregation is broken down by the `(provider, model_id)` pair and returns\n * raw Token sums (a model_id shared across providers is aggregated separately; never concatenated).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\n\nexport interface UsageRecordInsert {\n ts: string;\n date: string;\n projectId: string;\n agentId: string;\n sessionId: string;\n originSessionId: string | null;\n /** Provider group (pairs with modelId to form the attribution key). */\n provider: string;\n /** Upstream model id (pairs with provider). */\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n /** Request outcome; defaults to completed (success, carries tokens). Failed requests are stored with 0 tokens + status, for success-rate calculations. */\n status?: string;\n}\n\n/** Generic filter: date range + agent / model dimensions (cost center top bar switches by agent/model). */\nexport interface UsageFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Provider filter paired with modelId (the frontend dropdown always sends them together). */\n provider?: string;\n modelId?: string;\n}\n\n/**\n * Raw request success-rate counts for a single Model (paired reference).\n * `total` is **the success-rate denominator**: all requests minus aborted — the user\n * clicking \"stop\" is not a model failure, and counting it would make the success rate\n * drop every time stop is pressed. `aborted` is counted separately for display.\n */\nexport interface UsageStatusCount {\n provider: string;\n modelId: string;\n completed: number;\n total: number;\n aborted: number;\n failed: number;\n timeout: number;\n malformed: number;\n}\n\n/** Raw Token sums for a single Model (paired reference) — the smallest unit for cost conversion. */\nexport interface UsageModelSums {\n provider: string;\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n requests: number;\n}\n\n/** Raw Token sums by group key x Model. */\nexport interface UsageGroupModelSums extends UsageModelSums {\n key: string;\n}\n\n/** groupBy dimension -> column name allowlist (prevents injection; only these four columns can be group keys). */\nconst GROUP_COLUMNS: Record<UsageGroupBy, string> = {\n date: \"date\",\n agent: \"agent_id\",\n model: \"model_id\",\n session: \"session_id\",\n};\n\nconst SUM_COLUMNS = `COALESCE(SUM(cache_read), 0) AS cache_read,\n COALESCE(SUM(cache_write), 0) AS cache_write,\n COALESCE(SUM(output), 0) AS output,\n COALESCE(SUM(total), 0) AS total,\n COUNT(*) AS requests`;\n\nfunction toSums(r: Record<string, unknown>): UsageModelSums {\n return {\n provider: r.provider as string,\n modelId: r.model_id as string,\n cacheRead: r.cache_read as number,\n cacheWrite: r.cache_write as number,\n output: r.output as number,\n total: r.total as number,\n requests: r.requests as number,\n };\n}\n\nexport class UsageRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(r: UsageRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO usage_records\n (ts, date, project_id, agent_id, session_id, origin_session_id, provider, model_id,\n cache_read, cache_write, output, total, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.originSessionId,\n r.provider,\n r.modelId,\n r.cacheRead,\n r.cacheWrite,\n r.output,\n r.total,\n r.status ?? \"completed\",\n );\n }\n\n /** WHERE fragment (project + optional date/agent/model) plus named params. */\n private conds(\n projectId: string,\n f: UsageFilter,\n ): { where: string; params: Record<string, string> } {\n const conds = [\"project_id = :pid\"];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n if (f.provider !== undefined) {\n conds.push(\"provider = :provider\");\n params.provider = f.provider;\n }\n if (f.modelId !== undefined) {\n conds.push(\"model_id = :modelId\");\n params.modelId = f.modelId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Sums (broken down by paired reference): date range + optional agent/model filter. */\n bucketByModel(projectId: string, f: UsageFilter = {}): UsageModelSums[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map(toSums);\n }\n\n /** Grouped aggregation (group key x paired reference breakdown): date range + optional agent/model filter. */\n groupsByModel(\n projectId: string,\n groupBy: UsageGroupBy,\n f: UsageFilter = {},\n ): UsageGroupModelSums[] {\n const col = GROUP_COLUMNS[groupBy];\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ${col} AS key, provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY ${col}, provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({ key: r.key as string, ...toSums(r) }));\n }\n\n /**\n * Raw success-rate counts per Model (paired reference) (completed / non-aborted requests):\n * powers the cost center's \"Model Success Rate\" chart. The denominator excludes aborted\n * (user-initiated interruption); failure breakdowns (failed / timeout / malformed) are\n * also returned for hover display. Unknown statuses aren't broken out but still count\n * toward the denominator (conservative: anything non-completed counts as a failure).\n */\n statusByModel(projectId: string, f: UsageFilter = {}): UsageStatusCount[] {\n const { where, params } = this.conds(projectId, f);\n const count = (status: string) =>\n `COALESCE(SUM(CASE WHEN status = '${status}' THEN 1 ELSE 0 END), 0) AS ${status}`;\n const rows = this.db\n .prepare(\n `SELECT provider, model_id,\n ${count(\"completed\")},\n ${count(\"aborted\")},\n ${count(\"failed\")},\n ${count(\"timeout\")},\n ${count(\"malformed\")},\n COALESCE(SUM(CASE WHEN status <> 'aborted' THEN 1 ELSE 0 END), 0) AS total\n FROM usage_records WHERE ${where} GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({\n provider: r.provider as string,\n modelId: r.model_id as string,\n completed: r.completed as number,\n total: r.total as number,\n aborted: r.aborted as number,\n failed: r.failed as number,\n timeout: r.timeout as number,\n malformed: r.malformed as number,\n }));\n }\n\n /** Distinct agent_id values seen for this Project (for filter dropdowns). */\n distinctAgentIds(projectId: string): string[] {\n const rows = this.db\n .prepare(\n \"SELECT DISTINCT agent_id AS v FROM usage_records WHERE project_id = ? ORDER BY agent_id\",\n )\n .all(projectId);\n return rows.map((r) => r.v as string);\n }\n\n /** Distinct Model paired references seen for this Project (for filter dropdowns). */\n distinctModels(projectId: string): Array<{ provider: string; modelId: string }> {\n const rows = this.db\n .prepare(\n `SELECT DISTINCT provider, model_id FROM usage_records\n WHERE project_id = ? ORDER BY provider, model_id`,\n )\n .all(projectId);\n return rows.map((r) => ({ provider: r.provider as string, modelId: r.model_id as string }));\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM usage_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * users table repo: pure SQL wrapper, no business rules.\n * user_id is the login name (a semantic id, specified at creation, immutable).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface UserRow {\n userId: string;\n passwordHash: string;\n isAdmin: boolean;\n /** Still using the initial password (seeded / set by an admin); cleared to 0 once the user changes it. */\n passwordIsInitial: boolean;\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): UserRow {\n return {\n userId: r.user_id as string,\n passwordHash: r.password_hash as string,\n isAdmin: (r.is_admin as number) === 1,\n passwordIsInitial: (r.password_is_initial as number) === 1,\n createdAt: r.created_at as string,\n };\n}\n\nexport class UsersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: UserRow): void {\n this.db\n .prepare(\n \"INSERT INTO users (user_id, password_hash, is_admin, password_is_initial, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(\n row.userId,\n row.passwordHash,\n row.isAdmin ? 1 : 0,\n row.passwordIsInitial ? 1 : 0,\n row.createdAt,\n );\n }\n\n findById(userId: string): UserRow | null {\n const r = this.db.prepare(\"SELECT * FROM users WHERE user_id = ?\").get(userId);\n return r ? mapRow(r) : null;\n }\n\n /** All users (for the admin user backend), ordered by creation time ascending. */\n list(): UserRow[] {\n const rows = this.db.prepare(\"SELECT * FROM users ORDER BY created_at ASC, user_id ASC\").all();\n return rows.map(mapRow);\n }\n\n count(): number {\n const r = this.db.prepare(\"SELECT COUNT(*) AS n FROM users\").get();\n return (r?.n as number) ?? 0;\n }\n\n /** Update the password hash; isInitial marks whether the password was set by someone else (seed / admin). */\n updatePassword(userId: string, passwordHash: string, isInitial: boolean): void {\n this.db\n .prepare(\"UPDATE users SET password_hash = ?, password_is_initial = ? WHERE user_id = ?\")\n .run(passwordHash, isInitial ? 1 : 0, userId);\n }\n\n /** Used by admin user deletion and account-creation compensation paths (owned Projects must be cleaned up first). */\n delete(userId: string): void {\n this.db.prepare(\"DELETE FROM users WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * Auth middleware: cookie -> auth_session ->\n * user injected into c.var.\n *\n * Accessing a protected API while logged out -> 401 `{error:{code:\"unauthorized\"}}`.\n * CSRF (MVP): SameSite=Lax cookie + write requests only accept\n * `Content-Type: application/json` (an HTML form can't forge that Content-Type),\n * see the README security notes.\n */\nimport type { MiddlewareHandler } from \"hono\";\nimport { getCookie } from \"hono/cookie\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { UserRow } from \"../db/repos/users.js\";\nimport type { AuthService } from \"./service.js\";\n\n/** Session cookie name. */\nexport const SESSION_COOKIE = \"penguin_session\";\n\n/** Hono env: variables injected by the auth middleware. */\nexport type AppEnv = {\n Variables: {\n user: UserRow;\n };\n};\n\n/** Gets the current user (available after authMiddleware). */\nexport function currentUser(c: { var: { user: UserRow } }): UserRow {\n return c.var.user;\n}\n\nexport function authMiddleware(auth: AuthService): MiddlewareHandler<AppEnv> {\n return async (c, next) => {\n const token = getCookie(c, SESSION_COOKIE);\n const user = token ? auth.authenticate(token) : null;\n if (!user) {\n throw new HttpError(401, \"unauthorized\", \"未登录或登录已过期。\");\n }\n c.set(\"user\", user);\n await next();\n };\n}\n\nconst WRITE_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Content-Type defense for write requests: a write request with a Content-Type\n * other than application/json is rejected (a request with no Content-Type and an\n * empty body is let through — an HTML form always carries a form-type Content-Type).\n */\nexport const jsonOnlyWrites: MiddlewareHandler = async (c, next) => {\n if (WRITE_METHODS.has(c.req.method)) {\n const contentType = c.req.header(\"content-type\");\n if (contentType && !contentType.toLowerCase().startsWith(\"application/json\")) {\n throw new HttpError(415, \"unsupported_media_type\", \"写请求仅接受 application/json。\");\n }\n }\n await next();\n};\n","/**\n * Unified HTTP error: `{error: {code, message}}` response body,\n * with message in Chinese.\n *\n * Routes and services express business errors via throw HttpError; app-level onError\n * uniformly converges these into a JSON response, with unknown errors converged to 500\n * (never leaking internal details to the client).\n */\nimport type { Context } from \"hono\";\nimport type { ErrorBody } from \"../api/types.js\";\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport function errorBody(code: string, message: string): ErrorBody {\n return { error: { code, message } };\n}\n\n/**\n * Model missing a credential: the provider SDK throws this at **client-construction\n * time** (hit by both creating a Session and resuming a Session); the original message\n * is full of environment variable names, meaningless to a user — uniformly replaced\n * with a single actionable sentence. The frontend produces localized text by code\n * (message is only a fallback); see web's lib/api-error.ts.\n */\nexport function isMissingCredential(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /missing credentials|api[_ ]?key/i.test(message);\n}\n\nexport function modelCredentialMissing(modelId: string): HttpError {\n return new HttpError(\n 400,\n \"model_credential_missing\",\n `模型 ${modelId} 还没有可用的 API key,请先在「模型」页为它配置。`,\n );\n}\n\n/** app.onError handler: maps HttpError through as-is; everything else is logged and converged to 500. */\nexport function handleError(err: Error, c: Context): Response {\n if (err instanceof HttpError) {\n return c.json(errorBody(err.code, err.message), err.status as 400);\n }\n // Unknown error: print the stack for diagnosis, but never expose details externally.\n console.error(`[server] 未处理异常: ${err.stack ?? err.message}`);\n return c.json(errorBody(\"internal\", \"服务器内部错误。\"), 500);\n}\n","/**\n * Auth service: built-in admin seeding /\n * login / logout / password change / session validation.\n *\n * - No open registration: on startup, if there are no users at all, the built-in\n * admin `admin` is seeded (initial password admin123), and it adopts\n * `default_project`; all other users are created by an admin via the user\n * backend (admin-service).\n * - An initial password (whether seeded or set by an admin) is flagged with\n * password_is_initial, which the frontend uses to prompt for a password change soon.\n * - Sessions: a 32-byte random token, with only its sha256 hash stored in the DB;\n * valid for 7 days, with sliding renewal once less than 6 days remain.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { hashPassword, verifyPassword } from \"./password.js\";\n\nexport const MIN_PASSWORD_LENGTH = 8;\n\n/** Built-in admin: user_id and initial password (matches the README and login-page hint). */\nexport const ADMIN_USER_ID = \"admin\";\nexport const ADMIN_INITIAL_PASSWORD = \"admin123\";\n\nfunction sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function toUserInfo(row: UserRow): UserInfo {\n return {\n userId: row.userId,\n isAdmin: row.isAdmin,\n passwordIsInitial: row.passwordIsInitial,\n createdAt: row.createdAt,\n };\n}\n\nexport interface AuthServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n /** Provisions the initial Project at signup (injected by project-service, to avoid a circular dependency). */\n provisionInitialProject: (user: UserRow, isAdmin: boolean) => Promise<void>;\n sessionTtlMs: number;\n sessionRenewMs: number;\n now?: () => Date;\n}\n\nexport class AuthService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AuthServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n /**\n * Startup seeding (idempotent): creates the built-in admin and adopts\n * default_project when the users table is empty; if the initial Project fails,\n * the user row is rolled back and the server retries on next startup.\n */\n async seedAdmin(): Promise<void> {\n if (this.deps.users.count() > 0) return;\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await hashPassword(ADMIN_INITIAL_PASSWORD),\n isAdmin: true,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.provisionInitialProject(user, true);\n } catch (err) {\n this.deps.users.delete(user.userId);\n throw err;\n }\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const row = this.deps.users.findById(userId);\n const ok = row !== null && (await verifyPassword(password, row.passwordHash));\n if (!row || !ok) {\n throw new HttpError(401, \"invalid_credentials\", \"用户名或密码错误。\");\n }\n this.deps.authSessions.deleteExpired(this.now().toISOString());\n return { user: toUserInfo(row), token: this.issueSession(row.userId) };\n }\n\n /** Self password change (user settings): validates the old password, and on success clears the initial-password flag; the current session remains valid. */\n async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {\n const row = this.deps.users.findById(userId);\n if (!row || !(await verifyPassword(oldPassword, row.passwordHash))) {\n throw new HttpError(400, \"password_mismatch\", \"当前密码不正确。\");\n }\n if (newPassword.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"密码至少 8 个字符。\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(newPassword), false);\n }\n\n logout(token: string): void {\n this.deps.authSessions.delete(sha256Hex(token));\n }\n\n /** Validates the cookie token: returns null if expired/unknown; sliding renewal once less than 6 days remain. */\n authenticate(token: string): UserRow | null {\n const tokenHash = sha256Hex(token);\n const session = this.deps.authSessions.findByTokenHash(tokenHash);\n if (!session) return null;\n const now = this.now();\n const expiresAt = Date.parse(session.expiresAt);\n if (!(expiresAt > now.getTime())) {\n this.deps.authSessions.delete(tokenHash);\n return null;\n }\n if (expiresAt - now.getTime() < this.deps.sessionRenewMs) {\n this.deps.authSessions.touch(\n tokenHash,\n new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n );\n }\n return this.deps.users.findById(session.userId);\n }\n\n private issueSession(userId: string): string {\n const token = randomBytes(32).toString(\"base64url\");\n const now = this.now();\n this.deps.authSessions.insert({\n tokenHash: sha256Hex(token),\n userId,\n createdAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n });\n return token;\n }\n}\n","/**\n * Password hashing (slow-hash storage).\n *\n * Uses node:crypto's scrypt (built-in, no extra dependency, meets the same\n * slow-hash requirement as bcrypt/argon2). Storage format:\n * `scrypt$N$r$p$<salt b64>$<hash b64>` — parameters are stored alongside the hash,\n * so old hashes remain verifiable after future parameter tuning; comparison uses\n * timingSafeEqual to guard against timing side-channels.\n */\nimport { randomBytes, scrypt, timingSafeEqual } from \"node:crypto\";\n\nconst SCRYPT_N = 16384;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SALT_BYTES = 16;\nconst KEY_BYTES = 64;\n\nfunction scryptAsync(\n password: string,\n salt: Buffer,\n keyLen: number,\n n: number,\n r: number,\n p: number,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keyLen, { N: n, r, p, maxmem: 128 * 1024 * 1024 }, (err, key) => {\n if (err) reject(err);\n else resolve(key);\n });\n });\n}\n\n/** Generates a password hash in `scrypt$N$r$p$salt$hash` format. */\nexport async function hashPassword(password: string): Promise<string> {\n const salt = randomBytes(SALT_BYTES);\n const key = await scryptAsync(password, salt, KEY_BYTES, SCRYPT_N, SCRYPT_R, SCRYPT_P);\n return [\n \"scrypt\",\n String(SCRYPT_N),\n String(SCRYPT_R),\n String(SCRYPT_P),\n salt.toString(\"base64\"),\n key.toString(\"base64\"),\n ].join(\"$\");\n}\n\n/** Verifies a password; returns false if the stored string has an invalid format (never throws, so the login path can uniformly treat it as a credential error). */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(\"$\");\n if (parts.length !== 6 || parts[0] !== \"scrypt\") return false;\n const n = Number(parts[1]);\n const r = Number(parts[2]);\n const p = Number(parts[3]);\n if (!Number.isInteger(n) || !Number.isInteger(r) || !Number.isInteger(p)) return false;\n let salt: Buffer;\n let expected: Buffer;\n try {\n salt = Buffer.from(parts[4]!, \"base64\");\n expected = Buffer.from(parts[5]!, \"base64\");\n } catch {\n return false;\n }\n if (salt.length === 0 || expected.length === 0) return false;\n let actual: Buffer;\n try {\n actual = await scryptAsync(password, salt, expected.length, n, r, p);\n } catch {\n return false;\n }\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n","/**\n * Admin user-backend routes: only the built-in admin can use these (403 for non-admins).\n * GET|POST /api/admin/users, POST /api/admin/users/:userId/password, DELETE /api/admin/users/:userId.\n */\nimport { Hono } from \"hono\";\nimport type { AdminUserCreateResponse, AdminUsersResponse } from \"../../api/types.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function adminUsersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.use(\"*\", async (c, next) => {\n if (!c.var.user.isAdmin) {\n throw new HttpError(403, \"admin_required\", \"该操作仅管理员可执行。\");\n }\n await next();\n });\n\n app.get(\"/\", (c) => {\n return c.json({ users: deps.adminService.listUsers() } satisfies AdminUsersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const user = await deps.adminService.createUser(userId, password);\n return c.json({ user } satisfies AdminUserCreateResponse, 201);\n });\n\n app.post(\"/:userId/password\", async (c) => {\n const body = await readJson(c);\n const password = requireString(body, \"password\", { label: \"password\" });\n await deps.adminService.resetPassword(pathParam(c, \"userId\"), password);\n return c.body(null, 204);\n });\n\n app.delete(\"/:userId\", async (c) => {\n await deps.adminService.deleteUser(pathParam(c, \"userId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Hand-rolled request body validation helpers (fields follow TypeScript types;\n * this adds a runtime safety net).\n *\n * No validation library: each helper checks one basic shape, throwing a 400 HttpError on failure.\n */\nimport type { Context } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"./errors.js\";\n\nexport function badRequest(message: string): HttpError {\n return new HttpError(400, \"bad_request\", message);\n}\n\n/**\n * Get a path parameter (under sub-route mounting, hono infers string | undefined; the\n * route guarantees presence at runtime — treat a defensive missing value as 404).\n */\nexport function pathParam(c: Context, name: string): string {\n const v = c.req.param(name);\n if (v === undefined || v === \"\") {\n throw new HttpError(404, \"not_found\", \"路径参数缺失。\");\n }\n return v;\n}\n\n/**\n * Get a path parameter and validate the id (alphanumeric, underscore, and hyphen\n * only, to prevent path traversal). Hono decodes URL-encoded `%2F` into a single path\n * parameter; an id containing `/` or `..` passed straight into path construction could\n * escape the resource directory (cross-Project privilege escalation). So validate right\n * after reading the value — any invalid id is rejected with 404 (not leaking resource\n * existence), before any service-layer or path-construction code runs.\n */\nexport function requireValidId(c: Context, name: string): string {\n const v = pathParam(c, name);\n if (!isValidId(v)) {\n throw new HttpError(404, \"not_found\", \"资源不存在或无权访问。\");\n }\n return v;\n}\n\n/** Parse a positive-integer path parameter (e.g. Trace file index). */\nexport function positiveIntParam(c: Context, name: string): number {\n const v = Number.parseInt(pathParam(c, name), 10);\n if (!Number.isInteger(v) || v < 1) throw badRequest(`${name} 必须是正整数。`);\n return v;\n}\n\n/** Parse Trace pagination query params: offset >= 0 (default 0), limit 1-1000 (default 200). */\nexport function paginationQuery(c: Context): { offset: number; limit: number } {\n const offset = Number.parseInt(c.req.query(\"offset\") ?? \"0\", 10);\n const limit = Number.parseInt(c.req.query(\"limit\") ?? \"200\", 10);\n if (!Number.isInteger(offset) || offset < 0) throw badRequest(\"offset 必须是非负整数。\");\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw badRequest(\"limit 必须是 1~1000 的整数。\");\n }\n return { offset, limit };\n}\n\n/** Read the JSON request body (parse failure / non-object -> 400). */\nexport async function readJson(c: Context): Promise<Record<string, unknown>> {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw badRequest(\"请求体必须是合法 JSON。\");\n }\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n throw badRequest(\"请求体必须是 JSON 对象。\");\n }\n return body as Record<string, unknown>;\n}\n\nexport interface StringRule {\n minLen?: number;\n maxLen?: number;\n pattern?: RegExp;\n /** Display name for the field in error messages (defaults to key). */\n label?: string;\n}\n\nexport function requireString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string {\n const v = obj[key];\n const label = rule.label ?? key;\n if (typeof v !== \"string\") throw badRequest(`${label} 必须是字符串。`);\n if (rule.minLen !== undefined && v.length < rule.minLen) {\n throw badRequest(`${label} 长度至少 ${rule.minLen} 个字符。`);\n }\n if (rule.maxLen !== undefined && v.length > rule.maxLen) {\n throw badRequest(`${label} 长度不能超过 ${rule.maxLen} 个字符。`);\n }\n if (rule.pattern !== undefined && !rule.pattern.test(v)) {\n throw badRequest(`${label} 格式不合法。`);\n }\n return v;\n}\n\nexport function optionalString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string | undefined {\n if (obj[key] === undefined) return undefined;\n return requireString(obj, key, rule);\n}\n\nexport function requireEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T {\n const v = obj[key];\n if (typeof v !== \"string\" || !(values as readonly string[]).includes(v)) {\n throw badRequest(`${label} 必须是 ${values.join(\" / \")} 之一。`);\n }\n return v as T;\n}\n\nexport function optionalEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T | undefined {\n if (obj[key] === undefined) return undefined;\n return requireEnum(obj, key, values, label);\n}\n\nexport interface NumberRule {\n /** Require positive or -1 (Agent runtime parameter convention: >0 active, -1 disabled). */\n positiveOrMinusOne?: boolean;\n /** Require non-negative. */\n nonNegative?: boolean;\n /** Require integer. */\n integer?: boolean;\n label?: string;\n}\n\nexport function optionalNumber(\n obj: Record<string, unknown>,\n key: string,\n rule: NumberRule = {},\n): number | undefined {\n const v = obj[key];\n if (v === undefined) return undefined;\n const label = rule.label ?? key;\n if (typeof v !== \"number\" || !Number.isFinite(v)) throw badRequest(`${label} 必须是数字。`);\n if (rule.integer && !Number.isInteger(v)) throw badRequest(`${label} 必须是整数。`);\n if (rule.positiveOrMinusOne && !(v > 0 || v === -1)) {\n throw badRequest(`${label} 必须大于 0 或为 -1。`);\n }\n if (rule.nonNegative && v < 0) throw badRequest(`${label} 不能为负数。`);\n return v;\n}\n\n/** Validate a yyyy-mm-dd query parameter (defaults to undefined). */\nexport function optionalDateParam(value: string | undefined, label: string): string | undefined {\n if (value === undefined || value === \"\") return undefined;\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw badRequest(`${label} 必须是 YYYY-MM-DD 格式。`);\n }\n return value;\n}\n","/**\n * Auth routes: POST /api/auth/login | logout.\n * No self-registration: users are created by an admin in the user backend (/api/admin/users).\n * Login issues a cookie session; logout deletes the server-side session and clears the cookie.\n */\nimport { Hono } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport type { AuthResponse } from \"../../api/types.js\";\nimport { SESSION_COOKIE } from \"../../auth/middleware.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Session cookie attributes: HttpOnly, SameSite=Lax, 7 days. */\nfunction cookieOptions(c: { req: { header(name: string): string | undefined } }) {\n return {\n httpOnly: true,\n sameSite: \"Lax\" as const,\n path: \"/\",\n maxAge: 7 * 24 * 60 * 60,\n // Add Secure when the reverse proxy declares https.\n ...(c.req.header(\"x-forwarded-proto\") === \"https\" ? { secure: true } : {}),\n };\n}\n\nexport function authRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.post(\"/login\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const { user, token } = await deps.authService.login(userId, password);\n setCookie(c, SESSION_COOKIE, token, cookieOptions(c));\n return c.json({ user } satisfies AuthResponse);\n });\n\n app.post(\"/logout\", (c) => {\n const token = getCookie(c, SESSION_COOKIE);\n if (token) deps.authService.logout(token);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Current-user routes: GET /api/me, PUT /api/me/password, GET|PUT /api/me/prefs.\n * ui_prefs is free-form JSON (theme / lastProjectId / credentialGuideSeen, etc.): GET reads\n * it whole, PUT shallow-merges (PATCH semantics) — several independent writers each write\n * their own fields without clobbering each other.\n */\nimport { Hono } from \"hono\";\nimport type { MeResponse, PrefsResponse, UiPrefs } from \"../../api/types.js\";\nimport { toUserInfo } from \"../../auth/service.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function meRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n return c.json({ user: toUserInfo(c.var.user) } satisfies MeResponse);\n });\n\n // Self-service password change (user settings): validates the old password; on success, the initial-password prompt disappears from GET /api/me.\n app.put(\"/password\", async (c) => {\n const body = await readJson(c);\n const oldPassword = requireString(body, \"oldPassword\", { label: \"oldPassword\" });\n const newPassword = requireString(body, \"newPassword\", { label: \"newPassword\" });\n await deps.authService.changePassword(c.var.user.userId, oldPassword, newPassword);\n return c.body(null, 204);\n });\n\n app.get(\"/prefs\", (c) => {\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let prefs: UiPrefs = {};\n if (raw !== null) {\n try {\n prefs = JSON.parse(raw) as UiPrefs;\n } catch {\n prefs = {}; // Corrupted prefs fall back to an empty object\n }\n }\n return c.json({ prefs } satisfies PrefsResponse);\n });\n\n // PATCH semantics: the request body is **shallow-merged** into existing prefs, not a\n // full replace. prefs has several independent writers (lastProjectId /\n // credentialGuideSeen, etc., each writing their own field); a full replace would wipe\n // out each other's fields — e.g. writing lastProjectId when switching Projects would\n // clear credentialGuideSeen, breaking the \"show onboarding once ever\" guarantee.\n app.put(\"/prefs\", async (c) => {\n const body = await readJson(c);\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let current: UiPrefs = {};\n if (raw !== null) {\n try {\n current = JSON.parse(raw) as UiPrefs;\n } catch {\n current = {}; // Corrupted prefs fall back to an empty object (consistent with GET).\n }\n }\n const merged = { ...current, ...(body as UiPrefs) };\n deps.prefsRepo.set(c.var.user.userId, JSON.stringify(merged));\n return c.json({ prefs: merged } satisfies PrefsResponse);\n });\n\n return app;\n}\n","/**\n * User-level server event stream: GET /api/events (SSE user channel).\n * Carries cross-Session notifications (reserved for automated tasks); sends a `hello` handshake event on connect.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** The user channel's key in ChannelHub. */\nexport function userChannelKey(userId: string): string {\n return `user:${userId}`;\n}\n\nexport function eventsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n const channel = deps.channels.get(userChannelKey(c.var.user.userId));\n return sseEndpoint(c, channel, { initialEvents: [{ type: \"hello\" }] });\n });\n\n return app;\n}\n","/**\n * SSE endpoint adapter:\n * writes the runtime/channel event stream as an SSE response, shared by both the Session\n * channel and the user channel.\n *\n * - Response headers: text/event-stream, no-cache, `X-Accel-Buffering: no` (disables\n * buffering on reverse proxies);\n * - Heartbeat: writes a `: ping` comment line every 20s; the connection is torn down on write failure;\n * - Replay protocol: a fresh subscription without Last-Event-ID does not replay the buffer\n * (history is served by the messages endpoint) — it only sends the initial events the\n * caller supplied (pending approvals / hello). With a Last-Event-ID that hits the buffer,\n * replay resumes from there; on a miss, `resync_required` is sent first, then the\n * connection continues.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport type { Context } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { ServerEvent } from \"../api/types.js\";\nimport type { Channel, ChannelEvent, ChannelListener } from \"../runtime/channel.js\";\n\nconst HEARTBEAT_MS = 20_000;\n\nexport interface SseEndpointOptions {\n /** Initial server events to send privately after the subscription is established (in order): pending-approval replay / user channel hello. */\n initialEvents?: ServerEvent[];\n}\n\n/** Stream out a Channel as an SSE response. */\nexport function sseEndpoint(c: Context, channel: Channel, opts: SseEndpointOptions = {}): Response {\n const lastEventIdHeader = c.req.header(\"Last-Event-ID\");\n c.header(\"X-Accel-Buffering\", \"no\");\n c.header(\"Cache-Control\", \"no-cache\");\n\n return streamSSE(c, async (stream) => {\n let closed = false;\n let finish: () => void = () => {};\n const done = new Promise<void>((resolve) => {\n finish = () => {\n if (closed) return;\n closed = true;\n resolve();\n };\n });\n\n // Write serialization: SSE events must be written fully and in order; any write failure tears down the connection.\n let chain: Promise<void> = Promise.resolve();\n const enqueue = (write: () => Promise<unknown>): void => {\n chain = chain\n .then(async () => {\n if (!closed) await write();\n })\n .catch(() => finish());\n };\n const listener: ChannelListener = (evt: ChannelEvent) => {\n enqueue(() =>\n stream.writeSSE({\n data: evt.data,\n // Event id is an opaque string generated by the channel (`<epoch>-<seq>`), passed through as-is.\n id: evt.id,\n ...(evt.event !== undefined ? { event: evt.event } : {}),\n }),\n );\n };\n\n const unsubscribe = channel.subscribe(listener);\n // Subscribe first (to avoid dropping events in a race with broadcasts), then replay\n // synchronously — event order: buffered replay (or resync_required) -> initial events\n // (task_state snapshot / pending approvals / hello) -> live stream.\n if (lastEventIdHeader !== undefined) {\n const replay = channel.replayAfter(lastEventIdHeader);\n if (!replay.hit) {\n const resync: ServerEvent = { type: \"resync_required\" };\n channel.sendTo(listener, resync, \"server_event\");\n } else {\n for (const evt of replay.events) listener(evt);\n }\n }\n for (const event of opts.initialEvents ?? []) {\n channel.sendTo(listener, event, \"server_event\");\n }\n\n const heartbeat = setInterval(() => {\n enqueue(() => stream.write(\": ping\\n\\n\"));\n }, HEARTBEAT_MS);\n\n stream.onAbort(() => finish());\n try {\n await done;\n } finally {\n clearInterval(heartbeat);\n unsubscribe();\n }\n });\n}\n","/**\n * Project routes: GET|POST /api/projects, DELETE /api/projects/:p.\n */\nimport { Hono } from \"hono\";\nimport type { ProjectCreateResponse, ProjectsResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function projectsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projects = await deps.projectService.listProjects(c.var.user.userId);\n return c.json({ projects } satisfies ProjectsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const projectId = requireString(body, \"projectId\", { label: \"projectId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const project = await deps.projectService.createProject(c.var.user, projectId, name);\n return c.json({ project } satisfies ProjectCreateResponse, 201);\n });\n\n app.delete(\"/:projectId\", async (c) => {\n // Defensive id validation (FD-4): deleteProject constructs the project directory path and recursively deletes it.\n await deps.projectService.deleteProject(c.var.user.userId, requireValidId(c, \"projectId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Member authorization routes:\n * GET|POST /api/projects/:p/members, DELETE /api/projects/:p/members/:userId.\n * Reading requires access; adding/removing is owner-only (validated inside the service).\n */\nimport { Hono } from \"hono\";\nimport type { MemberAddResponse, MembersResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function membersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n // Defensive id validation (FD-4).\n const members = deps.projectService.listMembers(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n );\n return c.json({ members } satisfies MembersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const member = deps.projectService.addMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n userId,\n );\n return c.json({ member } satisfies MemberAddResponse, 201);\n });\n\n app.delete(\"/:userId\", (c) => {\n deps.projectService.removeMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n pathParam(c, \"userId\"),\n );\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Model & credential config routes:\n * GET|PUT /api/projects/:p/models, POST /api/projects/:p/models/test (the model reference\n * `(provider, modelId)` is sent as a pair in the request body, avoiding URL-encoding\n * issues). Any member can read (api_key is masked); only the owner can modify or test.\n */\nimport { Hono } from \"hono\";\nimport type {\n ModelRefDto,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelUpdateEntry,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Validate a paired reference object ({ provider, modelId }); shape mismatch throws 400. */\nfunction parseRef(value: unknown, label: string): ModelRefDto {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw badRequest(`${label} 必须是 { provider, modelId } 对象。`);\n }\n const r = value as Record<string, unknown>;\n return {\n provider: requireString(r, \"provider\", { minLen: 1, maxLen: 64, label: `${label}.provider` }),\n modelId: requireString(r, \"modelId\", { minLen: 1, maxLen: 200, label: `${label}.modelId` }),\n };\n}\n\n/** Validate the PUT request body and shape it into a ModelsUpdateRequest (rejects any shape errors). */\nfunction parseModelsUpdate(body: Record<string, unknown>): ModelsUpdateRequest {\n if (!Array.isArray(body.models)) throw badRequest(\"models 必须是数组。\");\n const models: ModelUpdateEntry[] = body.models.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`models[${i}] 必须是对象。`);\n }\n const m = item as Record<string, unknown>;\n const entry: ModelUpdateEntry = {\n provider: requireString(m, \"provider\", {\n minLen: 1,\n maxLen: 64,\n label: `models[${i}].provider`,\n }),\n modelId: requireString(m, \"modelId\", {\n minLen: 1,\n maxLen: 200,\n label: `models[${i}].modelId`,\n }),\n };\n if (m.displayName !== undefined) {\n if (typeof m.displayName !== \"string\" || m.displayName.length > 100) {\n throw badRequest(`models[${i}].displayName 必须是长度不超过 100 的字符串。`);\n }\n if (m.displayName) entry.displayName = m.displayName;\n }\n // A key change (either the provider group or the upstream id) goes through renamedFrom's paired old reference; unknown fields are ignored.\n if (m.renamedFrom !== undefined) {\n entry.renamedFrom = parseRef(m.renamedFrom, `models[${i}].renamedFrom`);\n }\n if (m.contextWindow !== undefined) {\n if (typeof m.contextWindow !== \"number\" || !(m.contextWindow > 0)) {\n throw badRequest(`models[${i}].contextWindow 必须是正数。`);\n }\n entry.contextWindow = m.contextWindow;\n }\n if (m.clientType !== undefined) {\n if (typeof m.clientType !== \"string\" || m.clientType.length > 64) {\n throw badRequest(`models[${i}].clientType 必须是长度不超过 64 的字符串。`);\n }\n // An empty string is treated as \"unspecified\", leaving AgentHub to infer it from modelId.\n if (m.clientType) entry.clientType = m.clientType;\n }\n if (m.vision !== undefined) {\n if (typeof m.vision !== \"boolean\") {\n throw badRequest(`models[${i}].vision 必须是布尔值。`);\n }\n entry.vision = m.vision;\n }\n if (m.pricing !== undefined) {\n const p = m.pricing as Record<string, unknown>;\n if (p === null || typeof p !== \"object\" || Array.isArray(p)) {\n throw badRequest(`models[${i}].pricing 必须是对象。`);\n }\n for (const key of [\"cacheRead\", \"cacheWrite\", \"output\"] as const) {\n const v = p[key];\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < 0) {\n throw badRequest(`models[${i}].pricing.${key} 必须是非负数字。`);\n }\n }\n entry.pricing = {\n cacheRead: p.cacheRead as number,\n cacheWrite: p.cacheWrite as number,\n output: p.output as number,\n };\n }\n if (m.apiKey !== undefined) {\n if (typeof m.apiKey !== \"string\" || m.apiKey.length === 0) {\n throw badRequest(`models[${i}].apiKey 必须是非空字符串。`);\n }\n entry.apiKey = m.apiKey;\n }\n if (m.clearApiKey !== undefined) {\n if (typeof m.clearApiKey !== \"boolean\") {\n throw badRequest(`models[${i}].clearApiKey 必须是布尔值。`);\n }\n entry.clearApiKey = m.clearApiKey;\n }\n if (m.baseUrl !== undefined) {\n if (m.baseUrl !== null && typeof m.baseUrl !== \"string\") {\n throw badRequest(`models[${i}].baseUrl 必须是字符串或 null。`);\n }\n entry.baseUrl = m.baseUrl as string | null;\n }\n return entry;\n });\n const req: ModelsUpdateRequest = { models };\n if (body.defaultModel !== undefined) {\n req.defaultModel = parseRef(body.defaultModel, \"defaultModel\");\n }\n if (body.visionModel !== undefined) {\n req.visionModel = parseRef(body.visionModel, \"visionModel\");\n }\n return req;\n}\n\nexport function modelsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.projectConfigService.getModels(projectId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseModelsUpdate(await readJson(c));\n return c.json(await deps.projectConfigService.updateModels(projectId, req));\n });\n\n // Connectivity test (owner): the model reference `(provider, modelId)` is sent as a pair\n // in the request body; sends one minimal request using that model's config. May include\n // not-yet-saved apiKey / baseUrl / clientType — when the model isn't in the config yet\n // (adding a custom model), everything is taken from the request body.\n app.post(\"/test\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: ModelTestRequest = {\n provider: requireString(body, \"provider\", { minLen: 1, maxLen: 64 }),\n modelId: requireString(body, \"modelId\", { minLen: 1, maxLen: 200 }),\n };\n if (body.apiKey !== undefined) {\n if (typeof body.apiKey !== \"string\") throw badRequest(\"apiKey 必须是字符串。\");\n if (body.apiKey) req.apiKey = body.apiKey;\n }\n if (body.clearApiKey !== undefined) {\n if (typeof body.clearApiKey !== \"boolean\") throw badRequest(\"clearApiKey 必须是布尔值。\");\n req.clearApiKey = body.clearApiKey;\n }\n // null = explicit clear (test against the draft, don't fall back to the stored value); empty string is treated as null.\n if (body.baseUrl !== undefined) {\n if (body.baseUrl !== null && typeof body.baseUrl !== \"string\") {\n throw badRequest(\"baseUrl 必须是字符串或 null。\");\n }\n req.baseUrl = body.baseUrl ? body.baseUrl : null;\n }\n if (body.clientType !== undefined) {\n if (typeof body.clientType !== \"string\") throw badRequest(\"clientType 必须是字符串。\");\n if (body.clientType) req.clientType = body.clientType;\n }\n return c.json(await deps.projectConfigService.testModel(projectId, req));\n });\n\n return app;\n}\n","/**\n * Vault environment variable routes:\n * GET|PUT /api/projects/:p/agents/:a/vault (Agent-level, agent_state/.vault.toml).\n * Any member can read (values masked); only the owner can modify; 404 if the Agent doesn't exist.\n */\nimport { Hono } from \"hono\";\nimport type { VaultEntryUpdate, VaultUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Validate the PUT request body and shape it into a VaultUpdateRequest (semantic checks like key-name rules live in the service layer). */\nfunction parseVaultUpdate(body: Record<string, unknown>): VaultUpdateRequest {\n if (!Array.isArray(body.entries)) throw badRequest(\"entries 必须是数组。\");\n const entries: VaultEntryUpdate[] = body.entries.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`entries[${i}] 必须是对象。`);\n }\n const e = item as Record<string, unknown>;\n const entry: VaultEntryUpdate = {\n key: requireString(e, \"key\", { minLen: 1, maxLen: 200, label: `entries[${i}].key` }),\n };\n if (e.value !== undefined) {\n if (typeof e.value !== \"string\" || e.value.length === 0) {\n throw badRequest(`entries[${i}].value 必须是非空字符串。`);\n }\n entry.value = e.value;\n }\n return entry;\n });\n return { entries };\n}\n\nexport function vaultRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.agentConfigService.getVault(projectId, agentId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseVaultUpdate(await readJson(c));\n const res = await deps.agentConfigService.updateVault(projectId, agentId, req);\n // Effective-value semantics: no hot update — an already-built runtime is neither\n // evicted nor reloaded; the new value only applies to Sessions created or resumed\n // afterward.\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Schedule routes:\n * GET|POST /api/projects/:p/agents/:a/schedules\n * GET|PUT|DELETE /api/projects/:p/agents/:a/schedules/:name (name is the file name)\n * Any member can read; only the owner can modify. The file is declarative intent:\n * POST/PUT fully replace the file, validation always goes through parseScheduleFile\n * (same rules as hand-edited files), and writes take effect immediately via reconciliation.\n */\nimport { createHash } from \"node:crypto\";\nimport { Hono } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport type { ScheduleItem, ScheduleStatus, SchedulesResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport {\n badRequest,\n optionalString,\n readJson,\n requireString,\n requireValidId,\n} from \"../validate.js\";\nimport type { ScheduleDefinition } from \"../../runtime/schedule-file.js\";\nimport {\n latestSlotAt,\n nextSlotAfter,\n parseScheduleFile,\n slotInWindow,\n} from \"../../runtime/schedule-file.js\";\nimport type { ScheduleStateRow } from \"../../db/repos/schedules.js\";\nimport {\n deleteScheduleFile,\n readScheduleFile,\n serializeSchedule,\n validateScheduleModelRef,\n writeScheduleFile,\n} from \"../../runtime/schedule-store.js\";\n\n/** Validate and shape the POST/PUT request body into file fields (semantic validation is left to parseScheduleFile). */\nfunction parseUpsertBody(body: Record<string, unknown>): {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n} {\n if (typeof body.enabled !== \"boolean\") throw badRequest(\"enabled 必须是布尔值。\");\n const prompt = requireString(body, \"prompt\", { minLen: 1, maxLen: 100_000 });\n const startAt = requireString(body, \"startAt\", { minLen: 1, maxLen: 100 });\n const period = optionalString(body, \"period\", { minLen: 1, maxLen: 20 });\n const endAt = optionalString(body, \"endAt\", { minLen: 1, maxLen: 100 });\n const sessionId = optionalString(body, \"sessionId\", { minLen: 1, maxLen: 200 });\n const workspace = optionalString(body, \"workspace\", { minLen: 1, maxLen: 4096 });\n const modelId = optionalString(body, \"modelId\", { minLen: 1, maxLen: 200 });\n const provider = optionalString(body, \"provider\", { minLen: 1, maxLen: 64 });\n return {\n prompt,\n enabled: body.enabled,\n startAt,\n ...(period !== undefined ? { period } : {}),\n ...(endAt !== undefined ? { endAt } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n };\n}\n\n/** Next scheduled fire time: none when disabled/invalid/done/missed; an undigested due slot counts as-is. */\nfunction nextFireAt(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n nowMs: number,\n): string | undefined {\n if (!def.enabled || state.invalidReason !== null) return undefined;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return undefined;\n const due = latestSlotAt(def, nowMs);\n if (\n due !== null &&\n slotInWindow(def, due) &&\n (state.lastSlotMs === null || due > state.lastSlotMs)\n ) {\n return new Date(due).toISOString();\n }\n const next = nextSlotAfter(def, nowMs);\n return next !== null ? new Date(next).toISOString() : undefined;\n}\n\n/** Displayed status precedence: invalid > done/missed (one-shot) > expired > enabled flag. */\nfunction statusOf(def: ScheduleDefinition, state: ScheduleStateRow, nowMs: number): ScheduleStatus {\n if (state.invalidReason !== null) return \"invalid\";\n if (def.periodMs === undefined && state.firedOnce) return \"done\";\n if (def.periodMs === undefined && state.missed) return \"missed\";\n if (def.endAtMs !== undefined && nowMs > def.endAtMs) return \"expired\";\n return def.enabled ? \"active\" : \"disabled\";\n}\n\nfunction toItem(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n queued: boolean,\n nowMs: number,\n): ScheduleItem {\n const next = nextFireAt(def, state, nowMs);\n return {\n name: def.name,\n prompt: def.prompt,\n enabled: def.enabled,\n startAt: def.startAt,\n ...(def.period !== undefined ? { period: def.period } : {}),\n ...(def.endAt !== undefined ? { endAt: def.endAt } : {}),\n ...(def.sessionId !== undefined ? { sessionId: def.sessionId } : {}),\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n status: statusOf(def, state, nowMs),\n ...(state.invalidReason !== null ? { invalidReason: state.invalidReason } : {}),\n ...(next !== undefined ? { nextFireAt: next } : {}),\n ...(state.lastFiredAt !== null ? { lastFiredAt: state.lastFiredAt } : {}),\n queued,\n ...(state.creatorUserId !== null ? { creatorUserId: state.creatorUserId } : {}),\n };\n}\n\n/** Schedule name in the path: same character rules as directories/files, validated before any path construction. */\nfunction requireScheduleName(raw: string | undefined): string {\n if (!raw || !isValidId(raw)) throw badRequest(\"定时任务名非法。\");\n return raw;\n}\n\nexport function scheduleRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const nowMs = Date.now();\n const res: SchedulesResponse = {\n schedules: entries.map((e) => toItem(e.def, e.state, e.queued, nowMs)),\n invalidFiles: invalid,\n };\n return c.json(res);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const name = requireScheduleName(requireString(body, \"name\", { minLen: 1, maxLen: 100 }));\n if (await readScheduleFile(deps.config.root, projectId, agentId, name)) {\n throw new HttpError(409, \"schedule_exists\", `定时任务已存在:${name}`);\n }\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name), 201);\n });\n\n app.get(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const item = await readItem(deps, projectId, agentId, name);\n return c.json(item);\n });\n\n app.put(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n if (!(await readScheduleFile(deps.config.root, projectId, agentId, name))) {\n throw new HttpError(404, \"schedule_not_found\", `定时任务不存在:${name}`);\n }\n const body = await readJson(c);\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name));\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const removed = await deleteScheduleFile(deps.config.root, projectId, agentId, name);\n if (!removed) throw new HttpError(404, \"schedule_not_found\", `定时任务不存在:${name}`);\n deps.scheduler.dropEntry(projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n\n/** Write + register creator + reconcile immediately (API changes take effect right away). */\nasync function upsert(\n deps: AppDeps,\n userId: string,\n projectId: string,\n agentId: string,\n name: string,\n body: Record<string, unknown>,\n): Promise<void> {\n const fields = parseUpsertBody(body);\n const raw = serializeSchedule(fields);\n const parsed = parseScheduleFile(name, raw);\n if (!parsed.ok) throw badRequest(`定时任务配置非法:${parsed.error}`);\n // At save time, verify the model reference resolves (resolveModelRef semantics; same rules as reconciliation) so we never persist a broken file.\n const refError = await validateScheduleModelRef(deps.config.root, projectId, parsed.def);\n if (refError !== null) throw badRequest(`定时任务配置非法:${refError}`);\n await writeScheduleFile(deps.config.root, projectId, agentId, name, raw);\n // Creator attribution: the API writer is the creator (falls back to the Project owner only for hand-edited files).\n deps.schedulesRepo.registerOrSync({\n projectId,\n agentId,\n name,\n startAtMs: parsed.def.startAtMs,\n defHash: createHash(\"sha1\").update(raw).digest(\"hex\"),\n creatorUserId: userId,\n });\n await deps.scheduler.reconcileAgent(projectId, agentId);\n}\n\nasync function readItem(\n deps: AppDeps,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleItem> {\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const entry = entries.find((e) => e.def.name === name);\n if (entry) return toItem(entry.def, entry.state, entry.queued, Date.now());\n const bad = invalid.find((i) => i.name === name);\n if (bad) throw badRequest(`定时任务文件非法:${bad.error}`);\n throw new HttpError(404, \"schedule_not_found\", `定时任务不存在:${name}`);\n}\n","/**\n * Schedule file parsing, validation, and trigger-time computation.\n *\n * `agent_state/schedule/<name>.toml` is declarative intent; the system never writes it\n * back. This module does pure parsing and pure time math only: an invalid file returns\n * an error (the scheduler skips it and records the error); runtime state (fired /\n * missed / disabled) doesn't live here — it belongs to SQLite (db/repos/schedules.ts).\n * Docs: /docs/configuration § \"Schedules\".\n */\nimport { parse as parseToml } from \"smol-toml\";\n\n/** `period` lower bound: below 5 minutes is treated as an invalid file (guards against runaway high-frequency tasks). */\nexport const MIN_PERIOD_MS = 5 * 60_000;\n\n/** A parsed schedule definition (the filename minus `.toml` is its identity). */\nexport interface ScheduleDefinition {\n name: string;\n /** The Prompt to send (required). */\n prompt: string;\n /** Enabled switch; disabled by default. */\n enabled: boolean;\n /** Original text of the first trigger time (for API echo, preserving the written form). */\n startAt: string;\n /** First trigger time (epoch ms). */\n startAtMs: number;\n /** Original text of the end time. */\n endAt?: string;\n /** Original text of the trigger period (e.g. `30m`, for API echo); undefined means a one-shot task. */\n period?: string;\n /** Trigger period (ms); undefined means a one-shot task. */\n periodMs?: number;\n /** End time (epoch ms); no more triggers once past it. */\n endAtMs?: number;\n /** The target Session to bind to; defaults to creating a new Session each time. */\n sessionId?: string;\n /** Workspace for new-Session mode (same semantics as manually starting a session; auto-creates a temp directory if unspecified). */\n workspace?: string;\n /** Model for new-Session mode (upstream id, paired with provider; defaults to the Project's default reference). */\n modelId?: string;\n /**\n * Vendor grouping for `model_id` (paired reference); when omitted, resolved per\n * resolveModelRef semantics — whether the reference is resolvable is validated by the\n * caller against config at reconciliation/save time (this module does pure parsing\n * and never touches config).\n */\n provider?: string;\n}\n\nexport type ScheduleParseResult =\n { ok: true; def: ScheduleDefinition } | { ok: false; error: string };\n\n/** Parse a fixed interval in `30m` / `12h` / `7d` form; returns null if invalid. */\nexport function parsePeriod(raw: string): number | null {\n const m = /^(\\d+)([mhd])$/.exec(raw.trim());\n if (!m) return null;\n const n = Number(m[1]);\n if (!Number.isInteger(n) || n <= 0) return null;\n const unit = m[2] === \"m\" ? 60_000 : m[2] === \"h\" ? 3_600_000 : 86_400_000;\n return n * unit;\n}\n\n/** Parse an ISO 8601 instant into epoch ms plus the original text for echo; returns null if invalid (smol-toml's date values are also accepted). */\nfunction parseInstant(value: unknown): { ms: number; raw: string } | null {\n if (value instanceof Date) {\n const ms = value.getTime();\n return Number.isNaN(ms) ? null : { ms, raw: value.toISOString() };\n }\n if (typeof value !== \"string\") return null;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? null : { ms, raw: value };\n}\n\n/**\n * Parse and validate a schedule file. A field with the wrong type invalidates the whole\n * file (the baseline for hand-edit tolerance is to never let bad config reach the\n * scheduler); unknown keys are ignored (forward compatibility).\n */\nexport function parseScheduleFile(name: string, raw: string): ScheduleParseResult {\n let parsed: unknown;\n try {\n parsed = parseToml(raw);\n } catch (err) {\n return {\n ok: false,\n error: `TOML 解析失败:${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (parsed === null || typeof parsed !== \"object\")\n return { ok: false, error: \"内容不是 TOML 表\" };\n const t = parsed as Record<string, unknown>;\n\n const prompt = t[\"prompt\"];\n if (typeof prompt !== \"string\" || prompt.trim() === \"\") {\n return { ok: false, error: \"缺少必填字段 prompt\" };\n }\n const enabled = t[\"enabled\"] === undefined ? false : t[\"enabled\"];\n if (typeof enabled !== \"boolean\") return { ok: false, error: \"enabled 必须是布尔值\" };\n\n const startAt = parseInstant(t[\"start_at\"]);\n if (startAt === null) return { ok: false, error: \"start_at 缺失或不是合法的 ISO 8601 时刻\" };\n\n let period: string | undefined;\n let periodMs: number | undefined;\n if (t[\"period\"] !== undefined) {\n if (typeof t[\"period\"] !== \"string\") return { ok: false, error: \"period 必须是字符串\" };\n const ms = parsePeriod(t[\"period\"]);\n if (ms === null) return { ok: false, error: \"period 必须形如 30m / 12h / 7d\" };\n if (ms < MIN_PERIOD_MS) return { ok: false, error: \"period 低于下限 5m\" };\n period = t[\"period\"].trim();\n periodMs = ms;\n }\n\n let endAt: { ms: number; raw: string } | undefined;\n if (t[\"end_at\"] !== undefined) {\n const parsedEnd = parseInstant(t[\"end_at\"]);\n if (parsedEnd === null) return { ok: false, error: \"end_at 不是合法的 ISO 8601 时刻\" };\n if (parsedEnd.ms <= startAt.ms) return { ok: false, error: \"end_at 必须晚于 start_at\" };\n endAt = parsedEnd;\n }\n\n let sessionId: string | undefined;\n if (t[\"session_id\"] !== undefined) {\n if (typeof t[\"session_id\"] !== \"string\" || t[\"session_id\"] === \"\") {\n return { ok: false, error: \"session_id 必须是非空字符串\" };\n }\n sessionId = t[\"session_id\"];\n }\n let workspace: string | undefined;\n if (t[\"workspace\"] !== undefined) {\n if (typeof t[\"workspace\"] !== \"string\" || t[\"workspace\"] === \"\") {\n return { ok: false, error: \"workspace 必须是非空字符串\" };\n }\n workspace = t[\"workspace\"];\n }\n let modelId: string | undefined;\n if (t[\"model_id\"] !== undefined) {\n if (typeof t[\"model_id\"] !== \"string\" || t[\"model_id\"] === \"\") {\n return { ok: false, error: \"model_id 必须是非空字符串\" };\n }\n modelId = t[\"model_id\"];\n }\n let provider: string | undefined;\n if (t[\"provider\"] !== undefined) {\n if (typeof t[\"provider\"] !== \"string\" || t[\"provider\"] === \"\") {\n return { ok: false, error: \"provider 必须是非空字符串\" };\n }\n provider = t[\"provider\"];\n }\n if (provider !== undefined && modelId === undefined) {\n return { ok: false, error: \"provider 仅与 model_id 成对使用(模型引用须成对给出)\" };\n }\n if (\n sessionId !== undefined &&\n (workspace !== undefined || modelId !== undefined || provider !== undefined)\n ) {\n return {\n ok: false,\n error: \"目标二选一:workspace 与 provider / model_id 仅用于新建 Session 模式\",\n };\n }\n\n return {\n ok: true,\n def: {\n name,\n prompt,\n enabled,\n startAt: startAt.raw,\n startAtMs: startAt.ms,\n ...(period !== undefined ? { period } : {}),\n ...(periodMs !== undefined ? { periodMs } : {}),\n ...(endAt !== undefined ? { endAt: endAt.raw, endAtMs: endAt.ms } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n },\n };\n}\n\n/**\n * Step from `start_at` by `period` and return the most recent scheduled time not later\n * than `nowMs`; null if `start_at` hasn't been reached yet. A one-shot task's only slot\n * is `start_at` itself.\n */\nexport function latestSlotAt(def: ScheduleDefinition, nowMs: number): number | null {\n if (nowMs < def.startAtMs) return null;\n if (def.periodMs === undefined) return def.startAtMs;\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs);\n return def.startAtMs + k * def.periodMs;\n}\n\n/** Whether a scheduled slot still falls within the `[start_at, end_at]` window (always true if there's no end_at). */\nexport function slotInWindow(def: ScheduleDefinition, slotMs: number): boolean {\n return def.endAtMs === undefined || slotMs <= def.endAtMs;\n}\n\n/**\n * The next scheduled time strictly after `nowMs` (used to display \"next trigger\");\n * for a one-shot task this only has a value while start_at hasn't been reached, and\n * returns null once past end_at.\n */\nexport function nextSlotAfter(def: ScheduleDefinition, nowMs: number): number | null {\n let next: number;\n if (nowMs < def.startAtMs) {\n next = def.startAtMs;\n } else if (def.periodMs === undefined) {\n return null;\n } else {\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs) + 1;\n next = def.startAtMs + k * def.periodMs;\n }\n return slotInWindow(def, next) ? next : null;\n}\n","/**\n * Schedule file access: `agent_state/schedule/<name>.toml`, where\n * the filename (a semantic name) is the identity. Reads are fault-tolerant (an invalid\n * file is recorded as an error and skipped by the caller); writes only go through the\n * API routes (the system never rewrites existing file content — PUT is a full-file\n * replacement expressing user intent).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { stringify as stringifyToml } from \"smol-toml\";\nimport { loadProjectConfig, resolveModelRef, scheduleDir } from \"@prismshadow/penguin-core\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { parseScheduleFile, type ScheduleParseResult } from \"./schedule-file.js\";\n\nexport interface ScheduleFileEntry {\n name: string;\n raw: string;\n parsed: ScheduleParseResult;\n}\n\n/** List all schedule files for this Agent (a missing directory is treated as empty). */\nexport async function listScheduleFiles(\n root: string,\n projectId: string,\n agentId: string,\n): Promise<ScheduleFileEntry[]> {\n const dir = scheduleDir(root, projectId, agentId);\n let names: string[];\n try {\n names = await fs.readdir(dir);\n } catch {\n return [];\n }\n const entries: ScheduleFileEntry[] = [];\n for (const file of names.sort()) {\n if (!file.endsWith(\".toml\")) continue;\n const name = file.slice(0, -\".toml\".length);\n let raw: string;\n try {\n raw = await fs.readFile(path.join(dir, file), \"utf8\");\n } catch {\n continue; // Deleted during reconciliation: revisit next round.\n }\n entries.push({ name, raw, parsed: parseScheduleFile(name, raw) });\n }\n return entries;\n}\n\nexport async function readScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleFileEntry | null> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n const raw = await fs.readFile(file, \"utf8\");\n return { name, raw, parsed: parseScheduleFile(name, raw) };\n } catch {\n return null;\n }\n}\n\n/** Serialize API fields into file content (validation uniformly goes through parseScheduleFile, avoiding two sets of rules). */\nexport function serializeSchedule(fields: {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n}): string {\n const table: Record<string, unknown> = {\n prompt: fields.prompt,\n enabled: fields.enabled,\n start_at: fields.startAt,\n ...(fields.period !== undefined ? { period: fields.period } : {}),\n ...(fields.endAt !== undefined ? { end_at: fields.endAt } : {}),\n ...(fields.sessionId !== undefined ? { session_id: fields.sessionId } : {}),\n ...(fields.workspace !== undefined ? { workspace: fields.workspace } : {}),\n ...(fields.provider !== undefined ? { provider: fields.provider } : {}),\n ...(fields.modelId !== undefined ? { model_id: fields.modelId } : {}),\n };\n return `${stringifyToml(table)}\\n`;\n}\n\n/**\n * Resolvability check for a schedule's model reference (shared by save and\n * reconciliation): when the definition has `model_id`, it's\n * resolved against Project config per resolveModelRef semantics — omitting provider is\n * only resolvable when model_id matches exactly one entry globally; zero hits or\n * ambiguity means unresolvable. Returns an error message (unresolvable / config read\n * failure), or null if resolvable (or no model reference at all).\n */\nexport async function validateScheduleModelRef(\n root: string,\n projectId: string,\n def: Pick<ScheduleDefinition, \"modelId\" | \"provider\">,\n): Promise<string | null> {\n if (def.modelId === undefined) return null;\n try {\n const cfg = await loadProjectConfig(root, projectId);\n resolveModelRef(cfg, def.modelId, def.provider);\n return null;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n}\n\n/** Write a schedule file to disk (full-file replacement for POST/PUT). */\nexport async function writeScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n raw: string,\n): Promise<void> {\n const dir = scheduleDir(root, projectId, agentId);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(path.join(dir, `${name}.toml`), raw, \"utf8\");\n}\n\n/** Delete a schedule file; returns false if it doesn't exist. */\nexport async function deleteScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<boolean> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n await fs.unlink(file);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Benchmark scoring routes:\n * GET /api/projects/:p/agents/:a/benchmarks (any member, read-only)\n * Returns the Agent's Benchmark list (title/description from benchmark_config.toml)\n * along with the evaluations[] from scoreboard.yaml.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { requireValidId } from \"../validate.js\";\n\nexport function benchmarksRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await deps.benchmarks.list(projectId, agentId));\n });\n\n return app;\n}\n","/**\n * Skill library & Agent-installed-Skills routes:\n * GET /api/skills # library groups & metadata (any logged-in user)\n * GET|POST /api/projects/:p/agents/:a/skills # installed list / install from library (any member)\n * DELETE /api/projects/:p/agents/:a/skills/:name # uninstall (any member)\n * Installing writes the library's SKILL.md verbatim to agent_state/skills/<name>/;\n * reinstalling overwrites with the library content (i.e. an update). The scope is small\n * enough to skip a service layer — routes call core's disk-writing functions directly.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport {\n installSkill,\n listInstalledSkills,\n removeSkill,\n skillsDir,\n} from \"@prismshadow/penguin-core\";\nimport { librarySkill, loadSkillGroups } from \"@prismshadow/penguin-skills\";\nimport type { LibrarySkill, SkillMetadata } from \"@prismshadow/penguin-skills\";\nimport type {\n AgentSkillsResponse,\n SkillLibraryResponse,\n SkillMetadataItem,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport { badRequest, readJson, requireValidId } from \"../validate.js\";\n\n/**\n * Strips the content off a LibrarySkill: the API only sends metadata; the full body is\n * written to disk on install and read by the model on demand. The optional short\n * description (shortDescription(Zh)) and custom icon (icon.svg source) are conditionally\n * passed through — both the library side (LibrarySkill) and the installed side (core\n * InstalledSkill) carry these fields.\n */\nfunction toMetadataItem(skill: SkillMetadata & { icon?: string }): SkillMetadataItem {\n return {\n name: skill.name,\n description: skill.description,\n ...(skill.shortDescription !== undefined ? { shortDescription: skill.shortDescription } : {}),\n ...(skill.shortDescriptionZh !== undefined\n ? { shortDescriptionZh: skill.shortDescriptionZh }\n : {}),\n ...(skill.icon !== undefined ? { icon: skill.icon } : {}),\n version: skill.version,\n updated: skill.updated,\n };\n}\n\n/** Library listing response: the files are the source of truth — read and parse the library directory fresh on every request (files are small, requests infrequent, no caching needed). */\nfunction libraryResponse(): SkillLibraryResponse {\n return {\n groups: loadSkillGroups().map((group) => ({\n id: group.id,\n title: group.title,\n ...(group.titleZh !== undefined ? { titleZh: group.titleZh } : {}),\n skills: group.skills.map(toMetadataItem),\n })),\n };\n}\n\n/** Validate the POST request body: names must be a non-empty array of strings. */\nfunction parseInstallNames(body: Record<string, unknown>): string[] {\n if (!Array.isArray(body.names) || body.names.length === 0) {\n throw badRequest(\"names 必须是非空数组。\");\n }\n return body.names.map((v, i) => {\n if (typeof v !== \"string\" || v.length === 0) {\n throw badRequest(`names[${i}] 必须是非空字符串。`);\n }\n return v;\n });\n}\n\n/** GET /api/skills: Skill library groups & metadata (any logged-in user; no Project check). */\nexport function skillLibraryRoutes(): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n app.get(\"/\", (c) => c.json(libraryResponse()));\n return app;\n}\n\n/** /api/projects/:p/agents/:a/skills: read, install, and uninstall are all Project-member operations. */\nexport function agentSkillsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n const listResponse = async (\n projectId: string,\n agentId: string,\n ): Promise<AgentSkillsResponse> => ({\n skills: (await listInstalledSkills(deps.config.root, projectId, agentId)).map(toMetadataItem),\n });\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await listResponse(projectId, agentId));\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const names = parseInstallNames(await readJson(c));\n // Verify all names up front before writing anything: if any name isn't in the library, reject the whole request rather than leaving a half-installed state.\n const skills: LibrarySkill[] = names.map((name) => {\n const skill = librarySkill(name);\n if (!skill) throw new HttpError(404, \"unknown_skill\", `Skill 库中不存在:${name}`);\n return skill;\n });\n for (const skill of skills) {\n await installSkill(deps.config.root, projectId, agentId, skill);\n }\n return c.json(await listResponse(projectId, agentId), 201);\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireValidId(c, \"name\");\n // Installed-check uses the same criterion as listInstalledSkills: skills/<name>/SKILL.md exists.\n const file = path.join(skillsDir(deps.config.root, projectId, agentId), name, \"SKILL.md\");\n try {\n await fs.access(file);\n } catch {\n throw new HttpError(404, \"not_found\", `Skill 未安装:${name}`);\n }\n await removeSkill(deps.config.root, projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Agent State export/import routes:\n * GET /api/projects/:p/agents/:a/export (any member; auto-packages if no snapshot exists, downloads tar.gz)\n * POST /api/projects/:p/agents/:a/import (owner only; version conflicts require a confirm flag)\n */\nimport fs from \"node:fs/promises\";\nimport { Hono } from \"hono\";\nimport type { AgentImportResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\n\n/** Import archive size cap: aligned with the global request body limit (stays within 20MB after base64). */\nconst MAX_ARCHIVE_BYTES = 14 * 1024 * 1024;\n\nexport function agentTransferRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/export\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const { file, fileName } = await deps.snapshots.exportArchive(projectId, agentId);\n const bytes = await fs.readFile(file);\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"Content-Type\": \"application/gzip\",\n \"Content-Disposition\": `attachment; filename=\"${fileName}\"`,\n },\n });\n });\n\n app.post(\"/import\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const dataBase64 = requireString(body, \"dataBase64\", { minLen: 1, maxLen: 20 * 1024 * 1024 });\n const confirm = body.confirm === true;\n let archive: Buffer;\n try {\n archive = Buffer.from(dataBase64, \"base64\");\n } catch {\n throw badRequest(\"dataBase64 不是合法的 base64。\");\n }\n if (archive.byteLength === 0) throw badRequest(\"导入包为空。\");\n if (archive.byteLength > MAX_ARCHIVE_BYTES) throw badRequest(\"导入包超过 14MB 上限。\");\n const { version } = await deps.snapshots.importArchive(projectId, agentId, archive, confirm);\n const res: AgentImportResponse = { version };\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Agent routes:\n * GET|POST /api/projects/:p/agents, DELETE /:agentId (owner only).\n * The list is the union of DB entries and directory scan results, including active\n * Session count, total Session count, and config last-modified time.\n */\nimport { Hono } from \"hono\";\nimport type { AgentCreateResponse, AgentsResponse, AgentSummary } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { settleWithin } from \"../settle.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Window size in days for the card's activity sparkline (last 30 days, including today). */\nconst ACTIVITY_DAYS = 30;\n\nexport function agentsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4): don't rely on the implicit invariant that requireProjectAccess always runs before path construction.\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const items = await deps.agentService.listAgents(projectId);\n const agents: AgentSummary[] = await Promise.all(\n items.map(async (item) => {\n const stats = await deps.sessionService.sessionStats(\n projectId,\n item.agentId,\n ACTIVITY_DAYS,\n );\n return {\n ...item,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, item.agentId),\n sessionCount: stats.sessionCount,\n sessionActivity: stats.activity,\n };\n }),\n );\n return c.json({ agents } satisfies AgentsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const agentId = requireString(body, \"agentId\", { label: \"agentId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const description = optionalString(body, \"description\", {\n maxLen: 2000,\n label: \"description\",\n });\n const item = await deps.agentService.createAgent(projectId, agentId, name, description);\n const agent: AgentSummary = {\n ...item,\n activeSessionCount: 0,\n sessionCount: 0,\n sessionActivity: Array.from({ length: ACTIVITY_DAYS }, () => 0),\n };\n return c.json({ agent } satisfies AgentCreateResponse, 201);\n });\n\n app.delete(\"/:agentId\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n // Deletion is a Project-level management operation: owner only.\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n // Mark as deleting and converge active runs (beginAgentDeletion): any new Task during\n // this window gets 409, preventing the race where a new task recreates the directory\n // and revives the Agent between abort and rm. Abort cleanup writes the Trace\n // asynchronously; wait for it to finish before removing the directory, and clear the\n // deleting flag once deletion completes (success or failure).\n const runnings = deps.manager.beginAgentDeletion(projectId, agentId);\n try {\n await settleWithin(runnings, 5000);\n await deps.agentService.deleteAgent(projectId, agentId);\n deps.sessionsRepo.deleteByAgent(projectId, agentId);\n } finally {\n deps.manager.endAgentDeletion(projectId, agentId);\n }\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Convergent wait for delete routes (currently used by agents DELETE; sessions DELETE\n * uses the same inline pattern and could later be unified onto this): waits for aborted\n * runs to wind down, up to ms milliseconds; returns whether all of them settled within\n * the window. The timer is unref'd so it never blocks process exit.\n */\nexport async function settleWithin(promises: Promise<unknown>[], ms: number): Promise<boolean> {\n if (promises.length === 0) return true;\n return Promise.race([\n Promise.allSettled(promises).then(() => true),\n new Promise<boolean>((resolve) => setTimeout(() => resolve(false), ms).unref?.()),\n ]);\n}\n","/**\n * Server directory browsing:\n * GET /api/projects/:p/dirs?path=<absolute>.\n *\n * Lets the user interactively pick a Workspace directory when creating a Session via\n * advanced mode. Defaults to the home directory of the account running the service, and\n * can be browsed all the way up to the root `/` — reachability is governed by OS file\n * permissions; the server no longer restricts browsing to within the Project directory\n * tree (same convention as workspace-guard). Lists subdirectories only, not files.\n *\n * `projectId` remains the authorization anchor: the caller must have access to that Project.\n */\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { DirListResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { HttpError } from \"../errors.js\";\nimport { requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function dirsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n\n // Default starting point: home directory; an explicit path must be absolute (the frontend always sends back the realpath result).\n const raw = c.req.query(\"path\");\n const target = raw && raw.trim() ? raw.trim() : os.homedir();\n if (!path.isAbsolute(target)) {\n throw new HttpError(400, \"dir_not_absolute\", \"目录必须是绝对路径。\");\n }\n\n let real: string;\n try {\n real = await fs.realpath(target);\n } catch {\n throw new HttpError(404, \"dir_not_found\", `目录不存在或不可访问:${target}。`);\n }\n const stat = await fs.stat(real);\n if (!stat.isDirectory()) {\n throw new HttpError(400, \"not_a_dir\", \"不是目录。\");\n }\n\n let dirents: import(\"node:fs\").Dirent[] = [];\n try {\n dirents = await fs.readdir(real, { withFileTypes: true });\n } catch {\n // No read permission: return an empty list instead of an error, so the user can still navigate back up.\n dirents = [];\n }\n const entries = dirents\n .filter((d) => d.isDirectory())\n .map((d) => ({ name: d.name, path: path.join(real, d.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = path.dirname(real);\n return c.json({\n path: real,\n parent: parent === real ? null : parent,\n entries,\n } satisfies DirListResponse);\n });\n\n return app;\n}\n","/**\n * Agent config routes (reads/writes system_config.yaml and AGENTS.md):\n * GET|PUT /api/projects/:p/agents/:a/config. Members can read and write (unrestricted).\n */\nimport { Hono } from \"hono\";\nimport type { AgentConfigResponse, AgentConfigUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalString, readJson, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function agentConfigRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: AgentConfigUpdateRequest = {};\n const agentsMd = optionalString(body, \"agentsMd\", { label: \"agentsMd\" });\n if (agentsMd !== undefined) req.agentsMd = agentsMd;\n if (body.config !== undefined) {\n if (body.config === null || typeof body.config !== \"object\" || Array.isArray(body.config)) {\n throw badRequest(\"config 必须是对象。\");\n }\n req.config = body.config as AgentConfigUpdateRequest[\"config\"];\n }\n // Fine-grained validation (numeric ranges / enums) is done inside agent-config-service.\n await deps.agentConfigService.updateConfig(projectId, agentId, req);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n return app;\n}\n","/**\n * Agent-level Trace browsing routes:\n * - GET /api/projects/:p/agents/:a/traces — drills down Agent -> date -> Session -> index (reverse order);\n * - GET /api/projects/:p/agents/:a/traces/:sessionId/:index (including /analysis) —\n * read-only Trace detail endpoints (FD-3): locate the Trace file directly by\n * (projectId, agentId, sessionId), without depending on the sessions table for\n * tracking — any entry visible in the directory tree (subagent child Sessions,\n * CLI-created Sessions) can be opened and read; access is enforced by requireProjectAccess.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { paginationQuery, positiveIntParam, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function agentTracesRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.traceService.agentTraces(projectId, agentId));\n });\n\n app.get(\"/:sessionId/:index\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(projectId, agentId, sessionId, index, offset, limit),\n );\n });\n\n app.get(\"/:sessionId/:index/analysis\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n return c.json(await deps.traceService.analyze(projectId, agentId, sessionId, index));\n });\n\n return app;\n}\n","/**\n * Usage statistics routes:\n * GET /api/projects/:p/usage?from&to&groupBy&agentId&provider&modelId\n * (model filter is paired: provider and modelId are given together).\n */\nimport { Hono } from \"hono\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalDateParam, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nconst GROUP_BYS: readonly UsageGroupBy[] = [\"date\", \"agent\", \"model\", \"session\"];\n\nexport function usageRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const groupByRaw = c.req.query(\"groupBy\") ?? \"date\";\n if (!(GROUP_BYS as readonly string[]).includes(groupByRaw)) {\n throw badRequest(`groupBy 必须是 ${GROUP_BYS.join(\" / \")} 之一。`);\n }\n const from = optionalDateParam(c.req.query(\"from\"), \"from\");\n const to = optionalDateParam(c.req.query(\"to\"), \"to\");\n const agentId = c.req.query(\"agentId\");\n const provider = c.req.query(\"provider\");\n const modelId = c.req.query(\"modelId\");\n return c.json(\n await deps.usageService.query(projectId, {\n groupBy: groupByRaw as UsageGroupBy,\n // Unattributed errors (login failures, process crashes, etc. with no Project\n // context) are visible only to admins: requireProjectAccess only guarantees\n // \"is a member of this Project\" — a regular member seeing another tenant's errors\n // would be a cross-tenant information leak.\n includeGlobalErrors: c.var.user.isAdmin,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n ...(agentId !== undefined && agentId !== \"\" ? { agentId } : {}),\n ...(provider !== undefined && provider !== \"\" ? { provider } : {}),\n ...(modelId !== undefined && modelId !== \"\" ? { modelId } : {}),\n }),\n );\n });\n\n return app;\n}\n","/**\n * Session routes.\n *\n * Two entry groups:\n * - Agent-level: GET|POST /api/projects/:p/agents/:a/sessions (list including run state / create);\n * - Session-level: /api/sessions/:sessionId/* (no projectId; looks up project_id via the\n * sessions index, then goes through requireProjectAccess; 404 if the index has no such Session).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { imageUrlMessage, scratchpadDir, userText } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n ApprovalMode,\n FilesStatResponse,\n MessagesResponse,\n ServerEvent,\n SessionCreateResponse,\n SessionResponse,\n SessionsResponse,\n TaskCreateResponse,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { SessionRow } from \"../../db/repos/sessions.js\";\nimport { assertWorkspaceAllowed } from \"../../services/workspace-guard.js\";\nimport { HttpError } from \"../errors.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport {\n badRequest,\n optionalEnum,\n optionalString,\n paginationQuery,\n pathParam,\n positiveIntParam,\n readJson,\n requireEnum,\n requireValidId,\n} from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { MAX_UPLOAD_BYTES } from \"../../services/workspace-files-service.js\";\n\n/** Max title length for manual renames: looser than the auto-generated 30-char limit, to accommodate users' own organizing conventions. */\nconst SESSION_TITLE_MAX = 120;\n\n/** Max path count and per-path length for a single files/stat check (message file-card candidates never exceed this scale). */\nconst STAT_MAX_PATHS = 100;\nconst STAT_MAX_PATH_LEN = 512;\n\nconst APPROVAL_MODES: readonly ApprovalMode[] = [\n \"allow-all\",\n \"deny-all\",\n \"read-only\",\n \"always-ask\",\n];\n\n/** Validate Prompt input parts: text or image (data: / http(s) URL). */\nfunction parseTaskInput(body: Record<string, unknown>): OmniMessage[] {\n const input = body.input;\n if (!Array.isArray(input) || input.length === 0) {\n throw badRequest(\"input 必须是至少包含一项的数组。\");\n }\n return input.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`input[${i}] 必须是对象。`);\n }\n const part = item as Record<string, unknown>;\n if (part.type === \"text\") {\n if (typeof part.text !== \"string\" || part.text.length === 0) {\n throw badRequest(`input[${i}].text 必须是非空字符串。`);\n }\n return userText(part.text);\n }\n if (part.type === \"image_url\") {\n const url = part.imageUrl;\n if (\n typeof url !== \"string\" ||\n !(url.startsWith(\"data:\") || url.startsWith(\"http://\") || url.startsWith(\"https://\"))\n ) {\n throw badRequest(`input[${i}].imageUrl 仅支持 data: 或 http(s) URL。`);\n }\n return imageUrlMessage(url);\n }\n throw badRequest(`input[${i}].type 必须是 text / image_url 之一。`);\n });\n}\n\n/** Agent-level entry: /api/projects/:p/agents/:a/sessions. */\nexport function agentSessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validity is checked before any path is constructed (FD-4: guards against agentId path traversal across Projects).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const sessions = await deps.sessionService.listSessions(projectId, agentId);\n return c.json({ sessions } satisfies SessionsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const modelId = optionalString(body, \"modelId\", { minLen: 1, label: \"modelId\" });\n const provider = optionalString(body, \"provider\", { minLen: 1, label: \"provider\" });\n // Model reference is submitted as a pair: provider can't appear without modelId (core does the same validation; this catches it early).\n if (provider !== undefined && modelId === undefined) {\n throw badRequest(\"指定了 provider 却未指定 modelId:模型引用须成对给出。\");\n }\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n let workspace = optionalString(body, \"workspace\", { minLen: 1, label: \"workspace\" });\n if (workspace !== undefined) {\n // An explicitly specified Workspace must be an existing directory (never auto-created); reachability is determined by file permissions.\n workspace = await assertWorkspaceAllowed({ workspace });\n }\n const session = await deps.sessionService.createSession({\n projectId,\n agentId,\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(approvalMode !== undefined ? { approvalMode } : {}),\n });\n return c.json({ session } satisfies SessionCreateResponse, 201);\n });\n\n return app;\n}\n\n/** Session-level entry point: /api/sessions/:sessionId/*. */\nexport function sessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n /** Look up ownership and check access (404 if the index has no such Session, or access is denied — never leaking existence). */\n const resolveSession = (c: Context<AppEnv>): SessionRow => {\n const sessionId = c.req.param(\"sessionId\");\n const row = sessionId ? deps.sessionsRepo.findById(sessionId) : null;\n if (!row) {\n throw new HttpError(404, \"session_not_found\", \"Session 不存在或无权访问。\");\n }\n try {\n deps.projectService.requireProjectAccess(c.var.user.userId, row.projectId);\n } catch {\n throw new HttpError(404, \"session_not_found\", \"Session 不存在或无权访问。\");\n }\n return row;\n };\n\n app.get(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const hasTrace = await deps.sessionService.hasTrace(row);\n return c.json({ session: deps.sessionService.toInfo(row, hasTrace) } satisfies SessionResponse);\n });\n\n app.patch(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n const archivedRaw = (body as Record<string, unknown>).archived;\n const archived = typeof archivedRaw === \"boolean\" ? archivedRaw : undefined;\n const titleRaw = (body as Record<string, unknown>).title;\n let title: string | undefined;\n if (titleRaw !== undefined) {\n if (typeof titleRaw !== \"string\") {\n throw new HttpError(400, \"invalid_title\", \"title 必须是字符串。\");\n }\n title = titleRaw.trim();\n if (!title || title.length > SESSION_TITLE_MAX) {\n throw new HttpError(400, \"invalid_title\", `title 需为 1–${SESSION_TITLE_MAX} 个字符。`);\n }\n }\n if (approvalMode === undefined && archived === undefined && title === undefined) {\n throw new HttpError(400, \"no_update\", \"缺少可更新字段(approvalMode / archived / title)。\");\n }\n let updated: SessionRow = { ...row };\n if (title !== undefined) {\n // Manual renaming takes priority over auto-generation: TitleGenerator only persists a title while it's still NULL.\n deps.sessionsRepo.updateTitle(row.sessionId, title);\n updated = { ...updated, title };\n }\n if (approvalMode !== undefined) {\n // Takes effect immediately: a running approve callback re-reads the DB on every decision.\n deps.sessionsRepo.updateApprovalMode(row.sessionId, approvalMode);\n updated = { ...updated, approvalMode };\n }\n if (archived !== undefined) {\n const at = archived ? new Date().toISOString() : null;\n deps.sessionsRepo.setArchived(row.sessionId, at);\n updated = { ...updated, archivedAt: at };\n }\n const hasTrace = await deps.sessionService.hasTrace(updated);\n return c.json({\n session: deps.sessionService.toInfo(updated, hasTrace),\n } satisfies SessionResponse);\n });\n\n app.delete(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n // Mark as being deleted and converge active runs (beginSessionDeletion): new\n // Tasks/compactions are always rejected with 409 during this window\n // (assertSessionNotDeleting), preventing the race where a new task recreates the\n // entry and Trace after abort but before the files are deleted, reviving an\n // already-deleted Session. Interrupt cleanup writes the Trace asynchronously, so we\n // wait for it to finish (≤5s cap) before deleting the files and index row; the\n // being-deleted marker is cleared once deletion finishes (success or failure).\n const runnings = deps.manager.beginSessionDeletion(row.sessionId);\n try {\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, 5000).unref?.()),\n ]);\n }\n await deps.traceService.deleteSessionTraces(row.projectId, row.agentId, row.sessionId);\n // The session-level scratchpad (model temp files + input images saved to disk for image-unsupported models) is deleted along with the session.\n await fs.rm(\n path.join(scratchpadDir(deps.config.root, row.projectId, row.agentId), row.sessionId),\n { recursive: true, force: true },\n );\n deps.sessionsRepo.deleteById(row.sessionId);\n } finally {\n deps.manager.endSessionDeletion(row.sessionId);\n }\n return c.body(null, 204);\n });\n\n // Session scratchpad files (e.g. input images saved to disk for image-unsupported\n // models): read by filename, so the conversation UI can render a message's\n // \"[attached image: <path>]\" attachment line back into an image. Restricted to this\n // session's own scratchpad directory (the filename must not contain a path\n // separator, blocking traversal); filenames include a timestamp and are globally\n // unique, so the response is marked immutable and long-cacheable.\n app.get(\"/:sessionId/scratchpad/:fileName\", async (c) => {\n const row = resolveSession(c);\n const fileName = c.req.param(\"fileName\") ?? \"\";\n if (!/^[A-Za-z0-9._-]+$/.test(fileName) || fileName.includes(\"..\")) {\n throw new HttpError(404, \"file_not_found\", \"文件不存在。\");\n }\n const filePath = path.join(\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n fileName,\n );\n let bytes: Buffer;\n try {\n bytes = await fs.readFile(filePath);\n } catch {\n throw new HttpError(404, \"file_not_found\", \"文件不存在。\");\n }\n const MIME_BY_EXT: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n };\n const mime = MIME_BY_EXT[path.extname(fileName).toLowerCase()] ?? \"application/octet-stream\";\n return c.body(new Uint8Array(bytes), 200, {\n \"content-type\": mime,\n \"cache-control\": \"private, max-age=31536000, immutable\",\n });\n });\n\n app.get(\"/:sessionId/messages\", async (c) => {\n const row = resolveSession(c);\n const messages = await deps.traceService.readMessages(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n return c.json({ messages } satisfies MessagesResponse);\n });\n\n app.get(\"/:sessionId/stream\", (c) => {\n const row = resolveSession(c);\n const channel = deps.channels.get(row.sessionId);\n // FD-1: the first event of every new subscription (including reconnects and resync\n // rebuilds) is always a snapshot of the current running state — the frontend treats\n // this as authoritative, eliminating input-area lockup or premature Task closure\n // caused by a stale running/idle in the list; followed by replaying all still-pending\n // approval requests.\n const initialEvents: ServerEvent[] = [\n { type: \"task_state\", state: deps.manager.statusOf(row.sessionId) },\n ...deps.manager.pendingApprovals(row.sessionId).map((p) => ({\n type: \"approval_request\" as const,\n toolCall: p.toolCall,\n ...(p.origin !== undefined ? { origin: p.origin } : {}),\n })),\n ];\n return sseEndpoint(c, channel, { initialEvents });\n });\n\n app.post(\"/:sessionId/tasks\", async (c) => {\n const row = resolveSession(c);\n const input = parseTaskInput(await readJson(c));\n // 202: the Task executes on the server, decoupled from the SSE connection; sessionId is the current actual id (the new id after self-heal).\n const { sessionId } = await deps.manager.startTask(row.sessionId, input);\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n });\n\n app.post(\"/:sessionId/approvals/:toolCallId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const decision = requireEnum(body, \"decision\", [\"allow\", \"deny\"] as const);\n const ok = deps.manager.decideApproval(row.sessionId, pathParam(c, \"toolCallId\"), decision);\n if (!ok) {\n throw new HttpError(404, \"approval_not_found\", \"该审批不存在或已被决定。\");\n }\n return c.body(null, 204);\n });\n\n app.post(\"/:sessionId/abort\", (c) => {\n const row = resolveSession(c);\n const aborted = deps.manager.abortTask(row.sessionId);\n // No Task in progress → 204 no-op; interrupt was triggered → 202 (wrap-up is completed by the SDK's \"interrupt cleanup\").\n return c.body(null, aborted ? 202 : 204);\n });\n\n app.post(\"/:sessionId/compact\", async (c) => {\n const row = resolveSession(c);\n const { sessionId } = await deps.manager.startCompact(row.sessionId);\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n });\n\n // —— Workspace file browsing (Files tab) ——\n\n app.get(\"/:sessionId/files\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n return c.json(await deps.workspaceFiles.list(row.workspace, rel));\n });\n\n app.get(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const download = c.req.query(\"download\") === \"1\";\n const { data, fileName, contentType, scriptable } = await deps.workspaceFiles.read(\n row.workspace,\n rel,\n );\n const disposition = download ? \"attachment\" : \"inline\";\n // Same-origin XSS defense: html/svg inline previews are always returned as plain\n // text (Workspace files may be Agent-generated and untrusted); downloads\n // (attachment) keep the real content type. Paired with nosniff to prevent MIME\n // sniffing from undoing this.\n const effectiveType = !download && scriptable ? \"text/plain; charset=utf-8\" : contentType;\n return new Response(new Uint8Array(data), {\n status: 200,\n headers: {\n \"Content-Type\": effectiveType,\n \"Content-Disposition\": `${disposition}; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n },\n });\n });\n\n // Bulk existence check (message file cards list only files that actually exist):\n // path-confinement resolution shares the same logic as files/content\n // (WorkspaceFilesService.statExisting reuses resolveRead); out-of-bounds or\n // resolution failures count as not-existing, always 200 — existence itself is the\n // question being answered, and a 4xx would only leak confinement details.\n app.post(\"/:sessionId/files/stat\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const paths = body.paths;\n if (\n !Array.isArray(paths) ||\n paths.length > STAT_MAX_PATHS ||\n !paths.every((p) => typeof p === \"string\" && p.length <= STAT_MAX_PATH_LEN)\n ) {\n throw badRequest(\n `paths 必须是字符串数组(≤${STAT_MAX_PATHS} 项,每项 ≤${STAT_MAX_PATH_LEN} 字符)。`,\n );\n }\n const existing = await deps.workspaceFiles.statExisting(row.workspace, paths as string[]);\n return c.json({ existing } satisfies FilesStatResponse);\n });\n\n app.put(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const body = await readJson(c);\n if (typeof body.dataBase64 !== \"string\") {\n throw badRequest(\"dataBase64 必须是 base64 字符串。\");\n }\n const data = Buffer.from(body.dataBase64, \"base64\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"上传文件超过 14MB 上限。\");\n }\n await deps.workspaceFiles.write(row.workspace, rel, data);\n return c.body(null, 204);\n });\n\n app.get(\"/:sessionId/traces\", async (c) => {\n const row = resolveSession(c);\n const files = await deps.traceService.listTraceFiles(row.projectId, row.agentId, row.sessionId);\n return c.json({ files });\n });\n\n app.get(\"/:sessionId/traces/:index\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(\n row.projectId,\n row.agentId,\n row.sessionId,\n index,\n offset,\n limit,\n ),\n );\n });\n\n app.get(\"/:sessionId/traces/:index/analysis\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n return c.json(\n await deps.traceService.analyze(row.projectId, row.agentId, row.sessionId, index),\n );\n });\n\n return app;\n}\n","/**\n * Workspace validation.\n *\n * When a user explicitly specifies a Workspace: after realpath normalization\n * (resolving `..` and symlinks), it's required to be an **existing directory**\n * (never auto-created). The directory location is not constrained to the\n * Project directory — a Workspace can be any path on the server, with actual\n * reachability governed by the file permissions of the OS account running the\n * service.\n * When no Workspace is specified, this module isn't involved (the SDK creates its\n * own temporary directory).\n */\nimport fs from \"node:fs/promises\";\nimport { HttpError } from \"../http/errors.js\";\n\n/**\n * Validates and returns the normalized (realpath) Workspace path.\n *\n * @throws 400 workspace_not_found: the path doesn't exist, isn't readable, or isn't a directory.\n */\nexport async function assertWorkspaceAllowed(args: { workspace: string }): Promise<string> {\n let ws: string;\n try {\n ws = await fs.realpath(args.workspace);\n } catch {\n throw new HttpError(\n 400,\n \"workspace_not_found\",\n `Workspace 不存在或不可访问:${args.workspace}。请指定一个已存在的目录,或留空以使用临时目录。`,\n );\n }\n const stat = await fs.stat(ws);\n if (!stat.isDirectory()) {\n throw new HttpError(400, \"workspace_not_found\", `Workspace 不是目录:${args.workspace}。`);\n }\n return ws;\n}\n","/**\n * Workspace file browsing: list directory / read\n * file (preview & download) / write file (upload). Security: a relative path, once\n * resolved, must stay inside the Workspace — a logical prefix check plus a realpath\n * check against the nearest existing ancestor (guards against `..` and symlink escapes).\n */\nimport fs from \"node:fs/promises\";\nimport { constants as fsc } from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceFilesResponse } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Per-file read cap (a safety limit since preview/download reads the whole file into memory). */\nconst MAX_READ_BYTES = 50 * 1024 * 1024;\n/** Upload cap (stays within the 20MB request body limit even after base64 encoding). */\nexport const MAX_UPLOAD_BYTES = 14 * 1024 * 1024;\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".htm\": \"text/html; charset=utf-8\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".json\": \"application/json\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".ts\": \"text/plain; charset=utf-8\",\n \".tsx\": \"text/plain; charset=utf-8\",\n \".py\": \"text/plain; charset=utf-8\",\n \".sh\": \"text/plain; charset=utf-8\",\n \".yaml\": \"text/plain; charset=utf-8\",\n \".yml\": \"text/plain; charset=utf-8\",\n \".toml\": \"text/plain; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".csv\": \"text/plain; charset=utf-8\",\n \".log\": \"text/plain; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".pdf\": \"application/pdf\",\n};\n\nexport interface WorkspaceFileContent {\n data: Buffer;\n fileName: string;\n contentType: string;\n /** Types whose same-origin inline rendering would execute scripts (html/svg): inline preview must fall back to plain text. */\n scriptable: boolean;\n}\n\nexport class WorkspaceFilesService {\n /** Canonical path (realpath) of the Workspace root; 404 if it doesn't exist. */\n private async realBase(workspace: string): Promise<string> {\n try {\n return await fs.realpath(path.resolve(workspace));\n } catch {\n throw new HttpError(404, \"workspace_missing\", \"该 Session 的 Workspace 已不存在。\");\n }\n }\n\n /**\n * Lexical containment check: whether target is inside base (including equal to\n * base). Uses path.relative rather than prefix concatenation, so it works when\n * base is the filesystem root (\"/\" concatenated with sep would produce a \"//\"\n * prefix that no subpath could ever match); only a full \"..\" segment is\n * compared, so a legitimate name like \"..foo\" isn't mistakenly rejected.\n */\n private isInside(target: string, base: string): boolean {\n const rel = path.relative(base, target);\n return rel !== \"..\" && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);\n }\n\n /** Lexical prefix check (a relative path, once resolved, must still be inside the Workspace); returns the absolute target path. */\n private lexicalTarget(base: string, rel: string): string {\n if (rel.includes(\"\\0\")) throw badRequest(\"path 非法。\");\n const target = path.resolve(base, rel === \"\" ? \".\" : rel);\n if (!this.isInside(target, base)) {\n throw badRequest(\"path 必须位于 Workspace 内。\");\n }\n return target;\n }\n\n private assertInside(real: string, realBase: string): void {\n if (!this.isInside(real, realBase)) {\n throw badRequest(\"path 必须位于 Workspace 内。\");\n }\n }\n\n /**\n * Read-path resolution: realpath the entire path (following all symlinks to get\n * a link-free canonical path), then check containment and **perform IO on the\n * canonical path** — since the canonical path contains no symlink segments at\n * all, this eliminates check-then-use TOCTOU escapes (an out-of-bounds symlink\n * is already resolved and rejected at the realpath step).\n */\n private async resolveRead(workspace: string, rel: string): Promise<string> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n let canonical: string;\n try {\n canonical = await fs.realpath(target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"文件不存在。\");\n }\n throw err;\n }\n this.assertInside(canonical, realBase);\n return canonical;\n }\n\n /**\n * Write-path resolution: realpaths the parent directory (whose canonical path\n * has no symlink segments) and checks containment, then appends the final\n * segment as the file name. When the parent directory is missing, it is safely\n * created (uploading a folder needs to preserve directory structure): first the\n * nearest **existing** ancestor is found and its canonical path checked against\n * the Workspace — this exposes it if a middle segment was preset as a symlink\n * pointing outside; the missing segments are then created recursively beneath it\n * (a brand-new directory can never be a symlink), followed by a second realpath\n * check after creation. The actual write opens with O_NOFOLLOW (refusing to\n * follow a symlink at the final segment), blocking the sandbox-escape pattern of\n * \"Agent presets a symlink -> an upload is used as leverage to overwrite a file\n * outside the sandbox\". Returns the canonical parent directory + file name.\n */\n private async resolveWriteParent(\n workspace: string,\n rel: string,\n ): Promise<{ dir: string; name: string }> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n const name = path.basename(target);\n if (name === \"\" || name === \".\" || name === \"..\") throw badRequest(\"path 必须是文件路径。\");\n const parent = path.dirname(target);\n let canonicalParent: string;\n try {\n canonicalParent = await fs.realpath(parent);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n let probe = parent;\n while (true) {\n try {\n this.assertInside(await fs.realpath(probe), realBase);\n break;\n } catch (probeErr) {\n if ((probeErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw probeErr;\n const up = path.dirname(probe);\n if (up === probe) throw badRequest(\"path 非法。\");\n probe = up;\n }\n }\n await fs.mkdir(parent, { recursive: true });\n canonicalParent = await fs.realpath(parent);\n }\n this.assertInside(canonicalParent, realBase);\n return { dir: canonicalParent, name };\n }\n\n /**\n * Batch existence check (a message's file card lists only files that actually\n * exist): each item goes through the same containment resolution as reading\n * (resolveRead); out-of-bounds, resolution failure, missing Workspace, or an\n * irregular file are all treated as non-existent — the card scenario only asks\n * \"can this be opened\", and throwing a 4xx would only add frontend branches while\n * leaking containment details. Returns the deduplicated existing items in input order.\n */\n async statExisting(workspace: string, rels: string[]): Promise<string[]> {\n const unique = [...new Set(rels)];\n const exists = await Promise.all(\n unique.map(async (rel) => {\n try {\n const stat = await fs.stat(await this.resolveRead(workspace, rel));\n return stat.isFile();\n } catch {\n return false;\n }\n }),\n );\n return unique.filter((_, i) => exists[i]);\n }\n\n /** List a directory: dirs come first, each group sorted by name; kind follows the symlink target (consistent with read behavior). */\n async list(workspace: string, rel: string): Promise<WorkspaceFilesResponse> {\n const dir = await this.resolveRead(workspace, rel);\n let dirents;\n try {\n dirents = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"目录不存在。\");\n }\n if ((err as NodeJS.ErrnoException).code === \"ENOTDIR\") {\n throw badRequest(\"path 不是目录。\");\n }\n throw err;\n }\n const entries = await Promise.all(\n dirents.map(async (d) => {\n let sizeBytes = 0;\n let mtime = \"\";\n // Dirent doesn't report the target type for a symlink, so stat (following the link) is used to determine dir/file.\n let isDir = d.isDirectory();\n try {\n const stat = await fs.stat(path.join(dir, d.name));\n sizeBytes = stat.size;\n mtime = stat.mtime.toISOString();\n isDir = stat.isDirectory();\n } catch {\n // A dangling symlink or similar: keep the entry, with size/time left at defaults.\n }\n return {\n name: d.name,\n kind: isDir ? (\"dir\" as const) : (\"file\" as const),\n sizeBytes,\n mtime,\n };\n }),\n );\n entries.sort((a, b) =>\n a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === \"dir\" ? -1 : 1,\n );\n return { path: rel, entries };\n }\n\n /** Read a file (preview/download): IO on the canonical path (resolveRead has already eliminated symlink escapes). */\n async read(workspace: string, rel: string): Promise<WorkspaceFileContent> {\n const file = await this.resolveRead(workspace, rel);\n let stat;\n try {\n stat = await fs.stat(file);\n } catch {\n throw new HttpError(404, \"path_not_found\", \"文件不存在。\");\n }\n if (stat.isDirectory()) throw badRequest(\"path 是目录。\");\n if (stat.size > MAX_READ_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"文件超过 50MB 读取上限。\");\n }\n const data = await fs.readFile(file);\n const ext = path.extname(file).toLowerCase();\n return {\n data,\n fileName: path.basename(file),\n contentType: CONTENT_TYPES[ext] ?? \"application/octet-stream\",\n scriptable: ext === \".html\" || ext === \".htm\" || ext === \".svg\",\n };\n }\n\n /**\n * Write a file (upload, overwriting a same-named one). If the parent directory\n * is missing, it's automatically created under sandbox checks (preserving\n * directory structure for folder uploads); the final segment is opened with\n * O_NOFOLLOW, refusing to follow a symlink to write outside the Workspace\n * (together with resolveWriteParent's canonical-parent check, this blocks\n * sandbox escapes).\n */\n async write(workspace: string, rel: string, data: Buffer): Promise<void> {\n if (rel === \"\" || rel.endsWith(\"/\")) throw badRequest(\"path 必须是文件路径。\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"上传文件超过 14MB 上限。\");\n }\n const { dir, name } = await this.resolveWriteParent(workspace, rel);\n const file = path.join(dir, name);\n // O_NOFOLLOW: open reports ELOOP if the final segment is a symlink, refusing to use it as leverage to overwrite a file outside the sandbox.\n const flags = fsc.O_WRONLY | fsc.O_CREAT | fsc.O_TRUNC | (fsc.O_NOFOLLOW ?? 0);\n let handle;\n try {\n handle = await fs.open(file, flags, 0o644);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ELOOP\") throw badRequest(\"path 不能是符号链接。\");\n if (code === \"ENOENT\") throw new HttpError(404, \"path_not_found\", \"父目录不存在。\");\n if (code === \"EISDIR\") throw badRequest(\"path 是目录。\");\n throw err;\n }\n try {\n await handle.writeFile(data);\n } finally {\n await handle.close();\n }\n }\n}\n","/**\n * SSE event channel.\n *\n * The Session channel and the user channel share this implementation:\n * - Event id is an opaque string `<epoch>-<seq>`: epoch is a random short string\n * generated when each Channel instance is created, seq is a monotonically increasing\n * integer within the channel. epoch necessarily changes when the channel is\n * recycled/recreated or the process restarts, so a stale Last-Event-ID always misses\n * and falls through to resync — this prevents a silent false-hit event loss when the\n * new epoch's event count happens to exceed the old id;\n * - A bounded ring buffer (most recent 1000 entries or 2MB, whichever comes first,\n * evicting the oldest on overflow) serves replay-on-reconnect via `Last-Event-ID`;\n * an evicted/unknown id is handled by the caller sending `resync_required`;\n * - Unicast (sendTo) is used for one-off replay at subscribe time (pending approvals /\n * resync / hello): it consumes a seq number but doesn't enter the buffer or get\n * broadcast — if that subscriber later reconnects with this id, the hit check is\n * still safe (seq is monotonic).\n *\n * This module only handles event numbering / buffering / dispatch, not HTTP — SSE\n * output is adapted at the routing layer.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport { randomUUID } from \"node:crypto\";\n\n/** A numbered channel event; `id` is `<epoch>-<seq>`, `data` is serialized single-line JSON. */\nexport interface ChannelEvent {\n id: string;\n /** SSE event name; omitted (OmniMessage) means no `event:` line. */\n event?: string;\n data: string;\n}\n\nexport type ChannelListener = (evt: ChannelEvent) => void;\n\nexport interface ChannelOptions {\n maxBufferCount?: number;\n maxBufferBytes?: number;\n}\n\nconst DEFAULT_MAX_COUNT = 1000;\nconst DEFAULT_MAX_BYTES = 2 * 1024 * 1024;\n\n/** Buffered entry: seq is stored separately so hit checks never need to parse the string id. */\ninterface BufferedEvent {\n seq: number;\n evt: ChannelEvent;\n}\n\nexport class Channel {\n /** Channel epoch: generated at instance creation, prefixed onto event ids (necessarily changes after recycle/recreate or restart). */\n readonly epoch: string = randomUUID().slice(0, 8);\n private nextSeq = 1;\n private buffer: BufferedEvent[] = [];\n private bufferBytes = 0;\n /** Max seq among evicted events (0 means never evicted): lower bound for hit checks. */\n private lastEvictedSeq = 0;\n private readonly listeners = new Set<ChannelListener>();\n private readonly maxCount: number;\n private readonly maxBytes: number;\n /** Timestamp of last activity (publish/subscription change), used for idle-reclaim checks. */\n lastActivityMs = Date.now();\n\n constructor(opts: ChannelOptions = {}) {\n this.maxCount = opts.maxBufferCount ?? DEFAULT_MAX_COUNT;\n this.maxBytes = opts.maxBufferBytes ?? DEFAULT_MAX_BYTES;\n }\n\n get subscriberCount(): number {\n return this.listeners.size;\n }\n\n /** Broadcast an event: number it, buffer it (evicting the oldest), notify all subscribers. */\n publish(data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n this.buffer.push(entry);\n this.bufferBytes += entry.evt.data.length;\n while (\n this.buffer.length > 0 &&\n (this.buffer.length > this.maxCount || this.bufferBytes > this.maxBytes)\n ) {\n const evicted = this.buffer.shift()!;\n this.bufferBytes -= evicted.evt.data.length;\n this.lastEvictedSeq = Math.max(this.lastEvictedSeq, evicted.seq);\n }\n for (const listener of this.listeners) listener(entry.evt);\n return entry.evt;\n }\n\n /** Unicast an event to a single subscriber: consumes a seq but doesn't buffer or broadcast (used for replay at subscribe time). */\n sendTo(listener: ChannelListener, data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n listener(entry.evt);\n return entry.evt;\n }\n\n subscribe(listener: ChannelListener): () => void {\n this.listeners.add(listener);\n this.lastActivityMs = Date.now();\n return () => {\n this.listeners.delete(listener);\n this.lastActivityMs = Date.now();\n };\n }\n\n /**\n * Compute replay from a Last-Event-ID (`<epoch>-<seq>`): a mismatched epoch (channel\n * recycled/recreated, process restarted, or malformed id) always misses; a matching\n * epoch hits the buffer (if no events after that seq have been evicted and the seq was\n * indeed assigned by this channel) and returns the buffered events after it; otherwise\n * miss (the caller should send `resync_required` first).\n */\n replayAfter(lastEventId: string): { hit: boolean; events: ChannelEvent[] } {\n const sep = lastEventId.lastIndexOf(\"-\");\n if (sep <= 0) return { hit: false, events: [] };\n const epoch = lastEventId.slice(0, sep);\n const seq = Number.parseInt(lastEventId.slice(sep + 1), 10);\n if (epoch !== this.epoch || !Number.isInteger(seq) || seq < 0) {\n return { hit: false, events: [] };\n }\n const hit = seq >= this.lastEvictedSeq && seq < this.nextSeq;\n if (!hit) return { hit: false, events: [] };\n return { hit: true, events: this.buffer.filter((e) => e.seq > seq).map((e) => e.evt) };\n }\n\n private makeEvent(data: unknown, event?: string): BufferedEvent {\n this.lastActivityMs = Date.now();\n const serialized = typeof data === \"string\" ? data : JSON.stringify(data);\n const seq = this.nextSeq++;\n const evt: ChannelEvent = { id: `${this.epoch}-${seq}`, data: serialized };\n if (event !== undefined) evt.event = event;\n return { seq, evt };\n }\n}\n\nconst DEFAULT_IDLE_MS = 30 * 60 * 1000;\nconst SWEEP_INTERVAL_MS = 60 * 1000;\n\nexport interface ChannelHubOptions {\n idleMs?: number;\n /**\n * Active check: keys for which this returns true are excluded from idle reclaim (app\n * assembly injects `manager.statusOf(key) !== \"idle\"`, so a running/compacting\n * Session channel is never reclaimed no matter how long since its last publish; a\n * user channel key looks like `user:<id>` and is always considered active).\n */\n isActive?: (key: string) => boolean;\n}\n\n/**\n * Channel collection: lazily created by key (Session id or `user:<user_id>`);\n * a channel whose Session is idle and has had no subscribers for over 30 minutes is\n * reclaimed, releasing its buffer as well.\n */\nexport class ChannelHub {\n private readonly channels = new Map<string, Channel>();\n private readonly timer: NodeJS.Timeout;\n private readonly idleMs: number;\n private readonly isActive: (key: string) => boolean;\n\n constructor(opts: ChannelHubOptions = {}) {\n this.idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;\n this.isActive = opts.isActive ?? (() => false);\n this.timer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n this.timer.unref?.();\n }\n\n get(key: string): Channel {\n let ch = this.channels.get(key);\n if (!ch) {\n ch = new Channel();\n this.channels.set(key, ch);\n }\n return ch;\n }\n\n peek(key: string): Channel | undefined {\n return this.channels.get(key);\n }\n\n /** Reclaim idle channels (skips active Sessions: no reclaim even without a publish while awaiting approval); `now` is injectable for tests. */\n sweep(now: number = Date.now()): void {\n for (const [key, ch] of this.channels) {\n if (this.isActive(key)) continue;\n if (ch.subscriberCount === 0 && now - ch.lastActivityMs > this.idleMs) {\n this.channels.delete(key);\n }\n }\n }\n\n dispose(): void {\n clearInterval(this.timer);\n this.channels.clear();\n }\n}\n","/**\n * Local date helpers (same convention as core's Trace date directories: local timezone\n * yyyy-mm-dd). Shared by the usage_records.date aggregation key and stats windows\n * (today / last 7 days / last 30 days).\n */\n\n/** Format a time as a local `yyyy-mm-dd` (4-digit year, zero-padded 2-digit month/day). */\nexport function formatLocalDate(date: Date): string {\n const year = date.getFullYear().toString().padStart(4, \"0\");\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/** Subtract N days from a local date (used for the start of the last-7-days / last-30-days windows). */\nexport function localDateMinusDays(date: Date, days: number): string {\n const d = new Date(date);\n d.setDate(d.getDate() - days);\n return formatLocalDate(d);\n}\n","/**\n * Error persistence: errors caught on the server are all\n * written to error_records through here, for display on the stats dashboard. Shape\n * mirrors usage-recorder — persist only raw facts, leave aggregation to query time.\n *\n * **The classification (kind) criterion is \"does a human need to step in\"**, not where\n * the error originated:\n *\n * - `expected`: anticipated by the system, has a defined handling path, part of normal\n * operation, no human needed — HTTP business errors (`HttpError`, mostly 4xx); LLM\n * `timeout` / `malformed` (the engine already reconnects and retries); tool execution\n * `failed` / `timeout` (the error is fed back to the model, and the Agent adjusts on\n * its own).\n * - `unexpected`: shouldn't happen, usually a bug or a config/environment fault,\n * **needs a human** — internal errors converged to 500; process crashes; runtime\n * errors escaping from background tasks (Session drive / usage persistence / title\n * generation / subagent registration); LLM `failed` (not retryable: auth failure,\n * invalid params, etc.).\n * - User-initiated actions **are not errors** and are never recorded: request/tool\n * `aborted` (user clicked \"stop\", or denied a tool).\n *\n * Determination: HTTP sources are inferred automatically from `HttpError` (preserving\n * existing behavior); other sources must pass `kind` explicitly at the capture site.\n * The frontend highlights unexpected by default; expected is still recorded without\n * losing information.\n *\n * Sources cover HTTP, Session drive, LLM requests, Environment (tool execution), usage\n * persistence, title generation, subagent registration, and process-level fallback;\n * among these, `llm` / `environment` errors are not expressed via throw (core converges\n * them into the message stream instead), and are fished out by stream-error-watcher from\n * the Session output stream.\n *\n * **This recorder never throws**: it's hooked onto app.onError, and throwing from\n * within it would turn error handling into infinite recursion; if persistence itself\n * fails (disk full / DB already closed, etc.), it's fine to drop that one record.\n *\n * **Short-window dedup (DEDUP_WINDOW_MS)**: error storms are the norm — someone scanning\n * the API produces a wall of 404s, or a tool fails repeatedly in a loop. Persisting each\n * one both write-amplifies and floods the table, and makes the dashboard's \"most recent\n * 20\" all the same error. So the same `(source, code, Project)` is persisted at most\n * once per window; repeats within the window are **dropped outright** (not persisted);\n * only an actual persist refreshes the timestamp, so a sustained storm leaves a steady\n * one record per window instead of being suppressed indefinitely.\n * **Tradeoff**: aggregate counts therefore **underestimate** — a storm of the same error\n * only counts once, check the logs for true frequency; in exchange, a single error storm\n * doesn't drown out error_records or the stats dashboard. The second line of defense is\n * ErrorsRepo's capacity cap. The dedup table (lastSeen) must stay bounded: past\n * DEDUP_KEYS_MAX, expired entries are cleared first, and if still over the limit the\n * whole table is cleared — better to miss some dedup than let it grow unbounded across\n * different error codes.\n */\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\n\n/** Capture-site source (maps one-to-one to error_records.source). */\nexport type ErrorSource =\n | \"http\"\n | \"session\"\n | \"llm\"\n | \"environment\"\n | \"usage\"\n | \"title\"\n | \"subagent\"\n | \"process\"\n | \"schedule\";\n\n/** Error classification: see file header — the criterion is \"does a human need to step in\". */\nexport type ErrorKind = \"expected\" | \"unexpected\";\n\n/** Attribution context (all optional: the login endpoint has no Project, and process-level fallback has no request at all). */\nexport interface ErrorContext {\n projectId?: string;\n agentId?: string;\n sessionId?: string;\n}\n\nexport interface ErrorRecordArgs {\n source: ErrorSource;\n /** The caught error (unknown: the value caught may not be an Error; failures from the message stream pass the reason text directly). */\n err: unknown;\n ctx?: ErrorContext;\n /** Semantic code (required for non-HTTP sources, e.g. session_run_failed); defaults to HttpError.code. */\n code?: string;\n /** HTTP status code; leave empty for non-HTTP sources. */\n status?: number;\n /** Explicit classification (see file header); defaults to inferring from `HttpError` — HTTP sources rely on this, other sources should pass it explicitly. */\n kind?: ErrorKind;\n}\n\n/** Message truncation length (keep only a readable summary; the full stack is still logged). */\nexport const MESSAGE_MAX = 500;\n\n/** Short-window dedup window: the same (source, code, Project) is persisted at most once per window (see the file header's tradeoff). */\nexport const DEDUP_WINDOW_MS = 2000;\n\n/** Cap on dedup table keys (bounded; over the limit, expired entries are cleared first, and if still over, the whole table is cleared). */\nexport const DEDUP_KEYS_MAX = 1000;\n\nfunction messageOf(err: unknown): string {\n const raw = err instanceof Error ? err.message : String(err);\n return raw.length > MESSAGE_MAX ? raw.slice(0, MESSAGE_MAX) : raw;\n}\n\nexport class ErrorRecorder {\n /** Dedup table: `source \\0 code \\0 projectId` → timestamp of the last **persist** (see file header). */\n private readonly lastSeen = new Map<string, number>();\n\n constructor(\n private readonly errors: ErrorsRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Record an error (synchronous, fails silently; same-window duplicates are dropped outright, see file header). */\n record(args: ErrorRecordArgs): void {\n try {\n const http = args.err instanceof HttpError ? args.err : null;\n const now = this.now();\n const projectId = args.ctx?.projectId ?? null;\n const code = args.code ?? http?.code ?? \"internal\";\n // Short-window dedup: coarse-grained to \"same kind of error for the same Project\"; repeats within the window aren't persisted.\n if (this.deduped(`${args.source}\\0${code}\\0${projectId ?? \"\"}`, now.getTime())) return;\n this.errors.insert({\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId,\n agentId: args.ctx?.agentId ?? null,\n sessionId: args.ctx?.sessionId ?? null,\n source: args.source,\n // Explicit classification takes priority; otherwise infer from HttpError (business error = expected, else unexpected).\n kind: args.kind ?? (http ? \"expected\" : \"unexpected\"),\n code,\n // Unexpected errors from HTTP sources are converged to 500 externally (matches handleError's response).\n status: args.status ?? http?.status ?? (args.source === \"http\" ? 500 : null),\n message: messageOf(args.err),\n });\n } catch {\n // See file header: if the recorder itself errors, dropping this one record is the only option — never rethrow.\n }\n }\n\n /** true if a same-kind error was already recorded within the window (drop it); otherwise register this persist timestamp and keep the dedup table bounded. */\n private deduped(key: string, nowMs: number): boolean {\n const last = this.lastSeen.get(key);\n if (last !== undefined && nowMs - last < DEDUP_WINDOW_MS) return true;\n this.lastSeen.set(key, nowMs);\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.evict(nowMs);\n return false;\n }\n\n /** Keep the dedup table bounded (see file header): clear expired entries first; if still over the limit (hundreds/thousands of distinct error codes erupting at once), clear it entirely. */\n private evict(nowMs: number): void {\n for (const [key, at] of this.lastSeen) {\n if (nowMs - at >= DEDUP_WINDOW_MS) this.lastSeen.delete(key);\n }\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.lastSeen.clear();\n }\n}\n\n/** Minimal dependency a capture site needs on the recorder (tests inject a fake; structurally matches SessionManager's UsageRecorderLike). */\nexport type ErrorSink = Pick<ErrorRecorder, \"record\">;\n","/**\n * Active Session runtime.\n *\n * Responsibilities:\n * - get-or-resume-or-heal: use it directly on an active-table hit; with a Trace,\n * recover via `agent.resumeSession`; a stale Session that was created but never run\n * and survived a process restart (no Trace) **self-heals** — recreated via\n * createSession using the index row's workspace/modelId, yielding a new session_id\n * and updating the index's primary key; the Task response body always returns the\n * current actual id;\n * - Per-Session mutual exclusion: only one Task/compaction may be in progress at a\n * time;\n * - run/compact drive: consumes the output stream in the background, publishing each\n * message to the SSE channel and handing it to usage-recorder for persistence;\n * on completion (including errors) resets to idle and pushes a `task_state` server\n * event;\n * - Approval registration and interrupt convergence: each approval decision re-reads\n * approval_mode from the DB (takes effect immediately); an interrupt first\n * converges pending approvals to deny, then aborts.\n *\n * The underlying implementation of get-or-resume-or-heal is injected via\n * `SessionLoader`: production uses the core SDK (createCoreSessionLoader), tests inject\n * a fake Session (issuing no real LLM requests).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n createAgent,\n findLatestTraceFile,\n isSessionMeta,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type {\n ApproveFn,\n CompactAvailability,\n OmniMessage,\n SessionMetaPayload,\n SessionTitleResult,\n TextPayload,\n} from \"@prismshadow/penguin-core\";\nimport type { ServerEvent, SessionStatus } from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport { ApprovalRegistry, makeApprove } from \"./approvals.js\";\nimport type { PendingApproval } from \"./approvals.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport { StreamErrorWatcher } from \"./stream-error-watcher.js\";\nimport type { TitleNotifier } from \"./title-generator.js\";\nimport type { UsageContext } from \"./usage-recorder.js\";\n\n/** 409 for when there's nothing to compact: give the specific reason rather than a one-size-fits-none message. */\nfunction compactUnavailable(why: Exclude<CompactAvailability, \"ok\">): HttpError {\n const messages: Record<typeof why, string> = {\n unsupported: \"该 Agent 未配置上下文压缩能力。\",\n empty: \"当前上下文没有可压缩的内容(尚无已完成的对话轮次)。\",\n just_compacted: \"上下文刚压缩过,此后还没有新的对话,无需再次压缩。\",\n };\n return new HttpError(409, \"nothing_to_compact\", messages[why]);\n}\n\n/** Minimal interface for a runtime Session (satisfied by core Session; tests may inject a fake implementation). */\nexport interface RuntimeSession {\n readonly sessionId: string;\n run(\n newMessages: OmniMessage[],\n opts: { approve: ApproveFn; signal: AbortSignal },\n ): AsyncGenerator<OmniMessage>;\n compact(opts: { signal: AbortSignal }): AsyncGenerator<OmniMessage>;\n /** Whether compaction is possible and why; when not ok, compact() yields no messages (see core ContextEngine.compactability). */\n compactability(): CompactAvailability;\n toolPermission(name: string): \"r\" | \"rw\" | undefined;\n /**\n * Out-of-band one-shot request for title generation (core `Session.generateTitle`,\n * writes no history/Trace). Material defaults to what the Session collects itself\n * (the first Task's text gathered during run); `material` overrides this for\n * subagents.\n */\n generateTitle(args?: {\n material?: { userText: string; assistantText: string };\n signal?: AbortSignal;\n }): Promise<SessionTitleResult>;\n}\n\n/** The underlying loader behind get-or-resume-or-heal. */\nexport interface SessionLoader {\n /**\n * Load a runtime Session from an index row: recover (with a Trace) or self-heal\n * rebuild (no Trace, session_id will change). Throws HttpError(409) for unrecoverable\n * cases such as a missing Workspace.\n */\n load(row: SessionRow): Promise<RuntimeSession>;\n}\n\n/** Production loader: the core SDK's resumeSession / createSession. */\nexport function createCoreSessionLoader(root: string): SessionLoader {\n return {\n async load(row: SessionRow): Promise<RuntimeSession> {\n const agent = await createAgent({\n root,\n projectId: row.projectId,\n agentId: row.agentId,\n });\n const located = await findLatestTraceFile(\n tracesDir(root, row.projectId, row.agentId),\n row.sessionId,\n );\n if (located) {\n // With a Trace: rebuild via \"Session Recovery\" (history injected via setHistory,\n // carrying over any residual state).\n // core's recognizable recovery failures (Workspace deleted / Model removed from\n // config / Trace missing session_meta, etc.) are converged to 409, preserving\n // the original message rather than bubbling up as 500.\n try {\n return await agent.resumeSession({ sessionId: row.sessionId });\n } catch (err) {\n // The credential key was deleted after the Session was created: only caught\n // here at recovery time; give the same actionable message.\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n }\n // No Trace (created but never run, and the process has restarted since): self-heal\n // rebuild. A missing Workspace → 409.\n try {\n const stat = await fs.stat(row.workspace);\n if (!stat.isDirectory()) throw new Error(\"not a directory\");\n } catch {\n throw new HttpError(\n 409,\n \"workspace_missing\",\n `该 Session 的 Workspace 已不存在:${row.workspace},无法继续。请新建 Session。`,\n );\n }\n try {\n return await agent.createSession({\n workspaceDir: row.workspace,\n modelId: row.modelId,\n provider: row.provider,\n });\n } catch (err) {\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n },\n };\n}\n\n/** A plain Error thrown by core recovery/self-heal rebuild → 409 (preserving the original, actionable message). */\nfunction toUnrecoverableError(err: unknown): HttpError {\n if (err instanceof HttpError) return err;\n return new HttpError(\n 409,\n \"session_unrecoverable\",\n err instanceof Error ? err.message : String(err),\n );\n}\n\nexport interface UsageRecorderLike {\n record(ctx: UsageContext, msg: OmniMessage): Promise<void>;\n}\n\nexport interface SessionManagerDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n loader: SessionLoader;\n recorder: UsageRecorderLike;\n /** Automatic Session title generation (optional: not injected in tests or when disabled). */\n titles?: TitleNotifier;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n}\n\n/** Active-table entry: a loaded runtime Session plus its running state. */\ninterface RuntimeEntry {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Vendor grouping for the Session's model (paired with modelId to form a model reference). */\n provider: string;\n modelId: string;\n session: RuntimeSession;\n status: SessionStatus;\n approvals: ApprovalRegistry;\n abort: AbortController | null;\n /** The in-flight drive Promise (awaited during graceful shutdown). */\n running: Promise<void> | null;\n /** Timestamp of last activity (refreshed on load / status flip / drive completion), used for idle-eviction checks. */\n lastActivityMs: number;\n}\n\n/** Active-table idle eviction: same convention as the SSE channel (an idle entry with no activity for 30 minutes releases its memory). */\nconst ENTRY_IDLE_MS = 30 * 60 * 1000;\nconst ENTRY_SWEEP_INTERVAL_MS = 60 * 1000;\n\n/** Cap on collected model text for title material (accumulation stops beyond this; the generator side also truncates further). */\nconst TITLE_EXCERPT_LIMIT = 4000;\n\n/** Composite Agent key (used as a Set key, avoiding projectId/agentId concatenation ambiguity). */\nfunction agentKey(projectId: string, agentId: string): string {\n return `${projectId}\\0${agentId}`;\n}\n\n/** If msg is a run_subagent tool call carrying a `prompt`, return its id and prompt (for use as the subagent's title); otherwise null. */\nfunction runSubagentCall(msg: OmniMessage): { toolCallId: string; prompt: string } | null {\n const p = msg.payload as {\n type?: string;\n name?: string;\n arguments?: string;\n tool_call_id?: string;\n };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call\" || p.name !== \"run_subagent\") return null;\n if (typeof p.arguments !== \"string\" || typeof p.tool_call_id !== \"string\") return null;\n try {\n const args = JSON.parse(p.arguments) as { prompt?: unknown };\n if (typeof args.prompt !== \"string\" || !args.prompt.trim()) return null;\n return { toolCallId: p.tool_call_id, prompt: args.prompt };\n } catch {\n return null; // Arguments were truncated/malformed: this call is doomed, no subagent will result\n }\n}\n\n/** The denied tool_call_id (approval_decision with decision ≠ allow); otherwise null. */\nfunction deniedToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; decision?: string; tool_call_id?: string };\n if (msg.type !== \"event_msg\" || p.type !== \"approval_decision\") return null;\n if (p.decision === \"allow\" || typeof p.tool_call_id !== \"string\") return null;\n return p.tool_call_id;\n}\n\n/** The tool_call_id of a parent-level tool call that has settled (a complete tool_call_output); otherwise null. */\nfunction settledToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; tool_call_id?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call_output\") return null;\n return typeof p.tool_call_id === \"string\" ? p.tool_call_id : null;\n}\n\n/** A subagent registered during this run, plus its title material. */\ninterface ChildSession {\n sessionId: string;\n agentId: string;\n modelId: string;\n /** The prompt of the run_subagent call that spawned it (user material for title generation, and the fallback title). */\n prompt: string;\n /** The model text the subagent itself produced (assistant material for title generation). */\n assistantExcerpt: string;\n}\n\n/** Predicate for a plain-text message on the main session (no origin): title material is drawn only from user/model text. */\nfunction isPlainText(role: \"user\" | \"assistant\") {\n return (msg: OmniMessage): msg is OmniMessage<TextPayload> => {\n const payload = msg.payload as { type?: string; role?: string };\n return (\n msg.type === \"model_msg\" &&\n payload.type === \"text\" &&\n payload.role === role &&\n (!msg.origin || msg.origin.length === 0)\n );\n };\n}\n\n/** For a nested message, the owning Session (end of the origin chain) and text of the model reply; null if it isn't model text. */\nfunction nestedAssistantText(msg: OmniMessage): { sessionId: string; text: string } | null {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"text\" || p.role !== \"assistant\") return null;\n if (!msg.origin || msg.origin.length === 0 || typeof p.text !== \"string\") return null;\n return { sessionId: msg.origin[msg.origin.length - 1]!, text: p.text };\n}\n\nexport class SessionManager {\n private readonly entries = new Map<string, RuntimeEntry>();\n /** Per-Session mutex (serializes get-or-load and status flips); auto-cleaned once the chain drains. */\n private readonly locks = new Map<string, Promise<unknown>>();\n private readonly log: (line: string) => void;\n /** Graceful-shutdown flag: once set, new Tasks/compactions are rejected (503). */\n private closed = false;\n /** Agents currently being deleted (key = agentKey): new Tasks/compactions are always rejected with 409 during this window. */\n private readonly deletingAgents = new Set<string>();\n /** Sessions currently being deleted (guards against the entry/Trace file being rebuilt and reviving it inside the deletion race window). */\n private readonly deletingSessions = new Set<string>();\n private readonly sweepTimer: NodeJS.Timeout;\n\n constructor(private readonly deps: SessionManagerDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n this.sweepTimer = setInterval(() => this.sweepIdle(), ENTRY_SWEEP_INTERVAL_MS);\n this.sweepTimer.unref?.();\n }\n\n // —— Query surface (used by Session listing / Agent active-count / SSE subscription replay) ——\n\n statusOf(sessionId: string): SessionStatus {\n return this.entries.get(sessionId)?.status ?? \"idle\";\n }\n\n pendingApprovalCount(sessionId: string): number {\n return this.entries.get(sessionId)?.approvals.size ?? 0;\n }\n\n pendingApprovals(sessionId: string): PendingApproval[] {\n return this.entries.get(sessionId)?.approvals.list() ?? [];\n }\n\n /** Number of Sessions for this Agent that are currently running / compacting. */\n activeCountForAgent(projectId: string, agentId: string): number {\n let n = 0;\n for (const e of this.entries.values()) {\n if (e.projectId === projectId && e.agentId === agentId && e.status !== \"idle\") n++;\n }\n return n;\n }\n\n /** Add a newly created Session to the active table (status idle), avoiding a redundant load on the next Task. */\n adopt(row: SessionRow, session: RuntimeSession): void {\n this.entries.set(row.sessionId, {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n lastActivityMs: Date.now(),\n });\n }\n\n // —— Task / compaction drive ——\n\n /**\n * Start a Task: get-or-load → 409\n * mutual-exclusion check → publish the input messages first → drive run in the\n * background. Returns the current actual session_id (the new id after self-heal).\n */\n async startTask(sessionId: string, input: OmniMessage[]): Promise<{ sessionId: string }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n this.assertIdle(entry);\n const channel = this.deps.channels.get(entry.sessionId);\n const ac = new AbortController();\n entry.status = \"running\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n // Publish the input messages first (visible to other subscribers; the Trace is\n // persisted by the SDK), then flip the running status.\n for (const msg of input) channel.publish(msg);\n this.publishState(entry, \"running\");\n\n const approve = makeApprove({\n // Re-reads approval_mode from the DB on every decision (a PATCH takes effect immediately).\n getMode: () => this.deps.sessions.findById(entry.sessionId)?.approvalMode ?? \"always-ask\",\n toolPermission: (name) => entry.session.toolPermission(name),\n registry: entry.approvals,\n publishRequest: (pending) =>\n this.publishEvent(entry, {\n type: \"approval_request\",\n toolCall: pending.toolCall,\n ...(pending.origin !== undefined ? { origin: pending.origin } : {}),\n }),\n });\n const gen = entry.session.run(input, { approve, signal: ac.signal });\n // Title material is collected by the core Session itself during run; here we only\n // keep this call's input user text, used both as the \"material present → attempt\n // generation\" criterion and as the fallback title source if the LLM call fails.\n const userExcerpt = input\n .filter(isPlainText(\"user\"))\n .map((m) => m.payload.text)\n .join(\"\\n\");\n entry.running = this.drive(entry, gen, { userExcerpt });\n return { sessionId: entry.sessionId };\n });\n }\n\n /** Manually compact the context: 409 if already running; compaction output also flows into the SSE channel. */\n async startCompact(sessionId: string): Promise<{ sessionId: string }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n this.assertIdle(entry);\n // When there's nothing to compact, core's compact() yields no messages at all: we\n // can't just return 202 and walk away, or the frontend would wait forever for a\n // compaction banner that never comes (this is exactly the \"/compact does nothing\n // after an interrupt\" complaint). Reject explicitly, and **say why** clearly —\n // \"just compacted\" and \"haven't talked yet\" share the same internal state\n // (sessionTurns === 0), but are two completely different messages to the user:\n // telling someone who just compacted that there's \"no completed conversation turn\n // yet\" tells them nothing.\n const why = entry.session.compactability();\n if (why !== \"ok\") throw compactUnavailable(why);\n const ac = new AbortController();\n entry.status = \"compacting\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"compacting\");\n const gen = entry.session.compact({ signal: ac.signal });\n entry.running = this.drive(entry, gen);\n return { sessionId: entry.sessionId };\n });\n }\n\n /** Submit an approval decision; returns false if the pending approval doesn't exist (already decided/unknown). */\n decideApproval(sessionId: string, toolCallId: string, decision: \"allow\" | \"deny\"): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry) return false;\n return entry.approvals.decide(toolCallId, decision);\n }\n\n /**\n * Interrupt the current Task/compaction: pending approvals converge to deny first,\n * then the AbortSignal fires. Returns false if nothing is in progress (the route\n * treats this as a 204 no-op).\n */\n abortTask(sessionId: string): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry || !entry.abort) return false;\n entry.approvals.denyAll();\n entry.abort.abort();\n return true;\n }\n\n /**\n * Before deleting a Project, converge all its active runs and clear them out of the\n * active table. Returns the in-flight drive Promises of the affected entries: the\n * caller (deleteProject) should await them before removing the directory, so that\n * interrupt-cleanup Trace writes don't recreate the directory after deletion.\n */\n abortProject(projectId: string): Promise<void>[] {\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n /**\n * Before deleting an Agent, converge all its active runs and clear them out of the\n * active table (same semantics as abortProject). Also marks this Agent as \"being\n * deleted\": new Tasks/compactions entering during the deletion process are always\n * rejected with 409 (assertAgentNotDeleting), closing the race window where a new\n * task recreates the directory and revives an already-deleted Agent between the\n * abortAgent snapshot and the directory removal. The caller must call\n * endAgentDeletion once deletion finishes (success or failure).\n */\n beginAgentDeletion(projectId: string, agentId: string): Promise<void>[] {\n this.deletingAgents.add(agentKey(projectId, agentId));\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId || entry.agentId !== agentId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n endAgentDeletion(projectId: string, agentId: string): void {\n this.deletingAgents.delete(agentKey(projectId, agentId));\n }\n\n /**\n * Before deleting a single Session, converge its active run and clear it out of the\n * active table (same semantics as beginAgentDeletion). Also marks this Session as\n * \"being deleted\": new Tasks/compactions entering during the deletion process are\n * always rejected with 409 (assertSessionNotDeleting), closing the race window where\n * a new task recreates the entry and Trace file, reviving an already-deleted Session\n * between the abort snapshot and the file removal. The caller must call\n * endSessionDeletion once deletion finishes (success or failure). Returns the\n * in-flight drive Promise: the caller should await it before deleting the Trace file,\n * so cleanup writes don't recreate the file.\n */\n beginSessionDeletion(sessionId: string): Promise<void>[] {\n this.deletingSessions.add(sessionId);\n const entry = this.entries.get(sessionId);\n if (!entry) return [];\n entry.approvals.denyAll();\n entry.abort?.abort();\n this.entries.delete(sessionId);\n return entry.running ? [entry.running] : [];\n }\n\n endSessionDeletion(sessionId: string): void {\n this.deletingSessions.delete(sessionId);\n }\n\n /** Graceful shutdown: reject new tasks (503), interrupt all active runs, and wait for them to finish (default ≤5s). */\n async shutdown(timeoutMs = 5000): Promise<void> {\n this.closed = true;\n clearInterval(this.sweepTimer);\n const pending: Promise<void>[] = [];\n for (const entry of this.entries.values()) {\n if (!entry.abort) continue;\n entry.approvals.denyAll();\n entry.abort.abort();\n if (entry.running) pending.push(entry.running);\n }\n if (pending.length === 0) return;\n await Promise.race([\n Promise.allSettled(pending).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs).unref?.()),\n ]);\n }\n\n /**\n * Active-table idle eviction: removes entries that are idle (idle status, no pending\n * approvals, no in-flight drive) and have been inactive past the timeout, releasing\n * the core Session's full in-memory history. This is purely memory reclamation: the\n * next access re-resumes via the loader, so correctness is unaffected. Lock-table\n * entries are auto-cleaned by withLock once their chain drains (including leftover\n * entries under the old id after self-heal). `now` / `idleMs` are injectable for\n * tests and timers.\n */\n sweepIdle(now: number = Date.now(), idleMs: number = ENTRY_IDLE_MS): void {\n for (const [key, entry] of this.entries) {\n if (entry.status !== \"idle\" || entry.approvals.size !== 0 || entry.running !== null) continue;\n if (now - entry.lastActivityMs <= idleMs) continue;\n this.entries.delete(key);\n }\n }\n\n // —— Internal ——\n\n private assertOpen(): void {\n if (this.closed) {\n throw new HttpError(503, \"shutting_down\", \"服务端正在关停,暂不接受新任务。\");\n }\n }\n\n /** The Agent owning this Session is being deleted → 409 (guards against directory recreation inside the deletion race window). */\n private assertAgentNotDeleting(sessionId: string): void {\n const row = this.deps.sessions.findById(sessionId);\n if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) {\n throw new HttpError(409, \"agent_deleting\", \"该 Agent 正在删除,暂不接受新任务。\");\n }\n }\n\n /** This Session is being deleted → 409 (guards against the entry/Trace being rebuilt and reviving it inside the deletion race window). */\n private assertSessionNotDeleting(sessionId: string): void {\n if (this.deletingSessions.has(sessionId)) {\n throw new HttpError(409, \"session_deleting\", \"该 Session 正在删除,暂不接受新任务。\");\n }\n }\n\n private assertIdle(entry: RuntimeEntry): void {\n if (entry.status === \"running\") {\n throw new HttpError(409, \"task_in_progress\", \"该 Session 已有进行中的 Task。\");\n }\n if (entry.status === \"compacting\") {\n throw new HttpError(409, \"compacting\", \"该 Session 正在压缩上下文,暂不接受新输入。\");\n }\n }\n\n /** get-or-resume-or-heal: use directly on an active-table hit; otherwise load via the loader, updating the index's primary key on self-heal. */\n private async ensureEntry(sessionId: string): Promise<RuntimeEntry> {\n const existing = this.entries.get(sessionId);\n if (existing) return existing;\n const row = this.deps.sessions.findById(sessionId);\n if (!row) {\n throw new HttpError(404, \"session_not_found\", \"Session 不存在或无权访问。\");\n }\n const session = await this.deps.loader.load(row);\n // The Session/Agent was marked for deletion while loading: discard the load result,\n // don't rebuild the entry (avoids reviving an orphaned Trace).\n this.assertSessionNotDeleting(row.sessionId);\n this.assertAgentNotDeleting(row.sessionId);\n let currentId = row.sessionId;\n if (session.sessionId !== row.sessionId) {\n // Self-heal produced a new session_id: update the index's primary key; the SSE\n // channel and pending state are naturally empty for it.\n this.deps.sessions.replaceId(row.sessionId, session.sessionId);\n currentId = session.sessionId;\n }\n const entry: RuntimeEntry = {\n sessionId: currentId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n lastActivityMs: Date.now(),\n };\n this.entries.set(currentId, entry);\n return entry;\n }\n\n /**\n * Drive the output stream in the background: publish each message + persist usage +\n * persist LLM/tool errors; on completion (including errors) resets to idle and pushes\n * the status. `titleSource` is passed only for Task runs (compaction doesn't generate\n * a title): it collects model text for automatic title generation.\n */\n private async drive(\n entry: RuntimeEntry,\n gen: AsyncGenerator<OmniMessage>,\n titleSource?: { userExcerpt: string },\n ): Promise<void> {\n const ctx: UsageContext = {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n provider: entry.provider,\n modelId: entry.modelId,\n };\n // LLM request failures and tool execution failures aren't expressed via throw (core\n // converges them into the message stream), so the try/catch below can't catch them:\n // the watcher inspects messages one by one and fishes them out for persistence\n // (subagent failures flow through this same stream too; see stream-error-watcher).\n const watcher = this.deps.errors\n ? new StreamErrorWatcher(this.deps.errors, {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n })\n : null;\n // Subagent (origin) registration: as soon as session_meta arrives, the child Session\n // is persisted so it appears immediately in the sidebar (the frontend picks it up\n // when it refreshes the list at task completion). The title material is \"the prompt\n // of the run_subagent call that spawned this subagent\" — the subagent's user input\n // is never replayed onto the parent stream (ContextEngine writes the Trace but never\n // yields it), so we can't rely on the subagent's first user message; instead we use\n // the run_subagent tool_call arguments immediately preceding it on the parent stream\n // (depth limited to 1, spawned in order, so taking the most recent one suffices).\n /** Subagents registered during this run (keyed by session id); titles are generated for each on completion. */\n const children = new Map<string, ChildSession>();\n // Unclaimed run_subagent prompts, queued in call order: a single round may spawn\n // multiple subagents in parallel, and a subagent's session_meta only carries the\n // session id (no tool_call_id), so pairing can only be approximated via FIFO (when\n // spawned in parallel and session_meta arrives out of order, two subagents' titles\n // may end up swapped — this only affects the displayed title). A call that will\n // never produce a subagent must be dequeued, or its prompt would be mismatched onto\n // the next subagent: this covers denied calls (approval_decision ≠ allow), and calls\n // that were approved but failed before spawning the subagent (e.g. agent_id doesn't\n // exist) — the latter is cleaned up when the parent-level tool_call_output settles;\n // if the call is still in the queue at that point, it never produced a session_meta.\n const subagentPrompts = new Map<string, string>();\n try {\n for await (const msg of gen) {\n // A parent-level (no origin) run_subagent call: record its prompt for the child\n // session_meta that arrives later to use as its title.\n if (!msg.origin || msg.origin.length === 0) {\n const call = runSubagentCall(msg);\n if (call) subagentPrompts.set(call.toolCallId, call.prompt);\n const denied = deniedToolCallId(msg);\n if (denied) subagentPrompts.delete(denied);\n const settled = settledToolCallId(msg);\n if (settled) subagentPrompts.delete(settled);\n } else if (isSessionMeta(msg)) {\n // Subagent registration is only a \"side effect\" — it must never interrupt the\n // main run flow on error: wrap the whole thing in a defensive try/catch.\n try {\n const child = this.registerChildSession(entry, msg, children);\n // Only a **direct** subagent (origin length 1) claims a queued parent-level\n // run_subagent prompt; deeper sessions are spawned by their own parent and\n // shouldn't consume from this queue.\n if (child && msg.origin!.length === 1) {\n const [pendingId] = subagentPrompts.keys();\n if (pendingId !== undefined) {\n child.prompt = subagentPrompts.get(pendingId) ?? \"\";\n subagentPrompts.delete(pendingId); // Consumed by this session_meta\n }\n }\n } catch (err) {\n this.log(\n `[subagent] 子会话登记失败: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.deps.errors?.record({\n source: \"subagent\",\n err,\n ctx,\n code: \"subagent_register_failed\",\n });\n }\n } else {\n // A subagent's model text: its title is generated from the subagent's **own\n // conversation**, so the material is accumulated here.\n const nested = nestedAssistantText(msg);\n const child = nested ? children.get(nested.sessionId) : undefined;\n if (nested && child && child.assistantExcerpt.length < TITLE_EXCERPT_LIMIT) {\n child.assistantExcerpt += (child.assistantExcerpt ? \"\\n\" : \"\") + nested.text;\n }\n }\n // Re-fetch the channel before every publish (matches publishEvent): the channel\n // may have been recycled and recreated during a long wait on approval, and\n // holding a stale reference would send output to an orphaned, detached channel.\n this.deps.channels.get(entry.sessionId).publish(msg);\n watcher?.observe(msg);\n try {\n await this.deps.recorder.record(ctx, msg);\n } catch (err) {\n this.log(`[usage] 落库失败: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"usage\", err, ctx, code: \"usage_insert_failed\" });\n }\n }\n } catch (err) {\n // The SDK doesn't normally throw (errors are converged into the message stream);\n // this is a defensive record here to avoid crashing the runtime.\n this.log(\n `[session] 运行异常: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,\n );\n this.deps.errors?.record({ source: \"session\", err, ctx, code: \"session_run_failed\" });\n } finally {\n // Wrap-up: persist any still-pending LLM failure and clear the tool-name cache (the watcher's state doesn't carry across runs).\n watcher?.close();\n entry.approvals.denyAll();\n entry.status = \"idle\";\n entry.abort = null;\n entry.running = null;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"idle\");\n if (titleSource && titleSource.userExcerpt.trim()) {\n // Attempt generation whenever there's user material; whether generation is\n // actually needed (title still NULL, etc.) is decided by the generator itself.\n // Material is collected by the core Session during run; here we only pass the\n // fallback text.\n this.deps.titles?.maybeGenerate(ctx, entry.session, {\n fallbackText: titleSource.userExcerpt,\n });\n }\n // A subagent's title is likewise generated by the model, with material being the\n // subagent's **own conversation**: the prompt that spawned it plus its own reply\n // (material the parent Session collects belongs to the parent, hence the explicit\n // override here). It piggybacks a one-shot request on the parent Session's bare\n // LLM (the child Session object never leaves the SDK); on failure/empty result the\n // generator falls back to the prompt's first line.\n for (const child of children.values()) {\n if (!child.prompt.trim()) continue;\n this.deps.titles?.maybeGenerate(\n // Bookkeeping: Session/Agent record the subagent (the title belongs to it),\n // but the model reference still uses ctx's **parent-Session** pair\n // (provider, modelId) — this one-shot request really does run on the parent\n // Session's bare LLM (a subagent may switch models via run_subagent's\n // model_id).\n { ...ctx, agentId: child.agentId, sessionId: child.sessionId },\n entry.session,\n {\n fallbackText: child.prompt,\n material: { userText: child.prompt, assistantText: child.assistantExcerpt },\n notifyOn: entry.sessionId, // Notify the frontend via the parent Session's SSE channel\n },\n );\n }\n }\n }\n\n /**\n * Register a subagent: persisted only when the origin message is session_meta\n * (agentId is derived from the agent_state path: `<…>/<agentId>/agent_state`).\n * **The title is left blank** — it's generated at the end of this run by the model\n * from the subagent's own conversation (see drive's finally), falling back to the\n * first line of the run_subagent prompt if generation fails. Idempotent (children\n * dedup + insertOrIgnore); a subagent has its own Trace, so it's visible in both the\n * list and the trace view. On successful registration, the entry is put into\n * `children` and returned; a duplicate session_meta returns null.\n */\n private registerChildSession(\n entry: RuntimeEntry,\n msg: OmniMessage,\n children: Map<string, ChildSession>,\n ): ChildSession | null {\n if (!isSessionMeta(msg)) return null;\n const childSid = msg.origin![msg.origin!.length - 1]!;\n if (children.has(childSid)) return null;\n const p = msg.payload as SessionMetaPayload;\n const agentId = path.basename(path.dirname(p.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return null;\n this.deps.sessions.insertOrIgnore({\n sessionId: childSid,\n projectId: entry.projectId,\n agentId,\n provider: p.provider,\n modelId: p.model_id,\n workspace: p.workspace,\n // A subagent's approvals are inherited from the parent Session; the index row is\n // inserted with defaults (matches the convention for Sessions discovered by the CLI).\n approvalMode: \"allow-all\",\n title: null,\n source: \"subagent\",\n createdAt: new Date().toISOString(),\n });\n // Make the subagent appear immediately in the sidebar: notify via the parent\n // Session's channel (a frontend currently watching the parent run refreshes its list in place).\n this.publishEvent(entry, {\n type: \"session_created\",\n projectId: entry.projectId,\n agentId,\n sessionId: childSid,\n source: \"subagent\",\n });\n const child: ChildSession = {\n sessionId: childSid,\n agentId,\n modelId: p.model_id,\n prompt: \"\",\n assistantExcerpt: \"\",\n };\n children.set(childSid, child);\n return child;\n }\n\n private publishState(entry: RuntimeEntry, state: SessionStatus): void {\n this.publishEvent(entry, { type: \"task_state\", state });\n }\n\n private publishEvent(entry: RuntimeEntry, event: ServerEvent): void {\n this.deps.channels.get(entry.sessionId).publish(event, \"server_event\");\n }\n\n /** Serialize (mutually exclude) execution by sessionId; cleans up the lock-table entry once its chain drains (avoids unbounded growth). */\n private async withLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n // What's stored in the chain is the already-caught version (used only for\n // sequencing, never propagates errors); the caller gets the original result from `next`.\n const prev = this.locks.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn);\n const settled: Promise<void> = next\n .then(\n () => undefined,\n () => undefined,\n )\n .then(() => {\n // Only delete if still the tail of the chain (no later waiter): preserves mutual-exclusion semantics.\n if (this.locks.get(sessionId) === settled) this.locks.delete(sessionId);\n });\n this.locks.set(sessionId, settled);\n return next;\n }\n}\n","/**\n * Tool call approval: ApproveFn factory + pending\n * approval registry.\n *\n * Approval mode semantics match the CLI (packages/cli/src/approval.ts):\n * allow-all auto-approves; deny-all auto-denies; read-only allows read-only tools\n * (permission===\"r\") and routes the rest to manual approval; always-ask routes\n * everything to manual approval. Routing to manual approval registers a pending entry\n * and pushes an `approval_request` via SSE, suspending until the frontend decides via\n * `POST /approvals/:toolCallId`; no timeout — pending approvals are resolved to deny\n * when the Task is interrupted (then proceeds through the abort flow).\n *\n * Every approval decision re-reads the current approval_mode (`getMode` reads the DB),\n * so mode changes take effect immediately.\n * Docs: /docs/tools § \"Approval\".\n */\nimport type { ApprovalMode } from \"../api/types.js\";\nimport type {\n ApprovalDecision,\n ApproveFn,\n OmniMessage,\n ToolCallPayload,\n} from \"@prismshadow/penguin-core\";\n\nexport interface PendingApproval {\n toolCall: OmniMessage<ToolCallPayload>;\n origin?: string[];\n}\n\ninterface PendingEntry extends PendingApproval {\n resolve: (decision: ApprovalDecision) => void;\n}\n\n/** Pending approval registry (key = tool_call_id), one per Session runtime. */\nexport class ApprovalRegistry {\n private readonly pending = new Map<string, PendingEntry>();\n\n get size(): number {\n return this.pending.size;\n }\n\n /** All currently pending approvals (for subscription replay). */\n list(): PendingApproval[] {\n return [...this.pending.values()].map(({ toolCall, origin }) => ({\n toolCall,\n ...(origin !== undefined ? { origin } : {}),\n }));\n }\n\n /** Register and wait for a decision. Re-registering the same id (defensive) resolves the old entry as deny. */\n wait(toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> {\n const id = toolCall.payload.tool_call_id;\n this.pending.get(id)?.resolve(\"deny\");\n return new Promise<ApprovalDecision>((resolve) => {\n const entry: PendingEntry = {\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n resolve: (decision) => {\n this.pending.delete(id);\n resolve(decision);\n },\n };\n this.pending.set(id, entry);\n });\n }\n\n /** Submit a decision; returns false if not found (already decided/unknown). */\n decide(toolCallId: string, decision: ApprovalDecision): boolean {\n const entry = this.pending.get(toolCallId);\n if (!entry) return false;\n entry.resolve(decision);\n return true;\n }\n\n /** Interruption convergence: resolve all pending approvals as deny. */\n denyAll(): void {\n for (const entry of [...this.pending.values()]) entry.resolve(\"deny\");\n }\n}\n\n/**\n * Build the approve callback: re-reads the approval mode on every call; when routed to\n * manual approval, registers a pending entry and suspends after pushing an\n * `approval_request` server event via `publishRequest`.\n */\nexport function makeApprove(args: {\n getMode: () => ApprovalMode;\n toolPermission: (name: string) => \"r\" | \"rw\" | undefined;\n registry: ApprovalRegistry;\n publishRequest: (pending: PendingApproval) => void;\n}): ApproveFn {\n const { getMode, toolPermission, registry, publishRequest } = args;\n const manual = (toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> => {\n const promise = registry.wait(toolCall);\n publishRequest({\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n });\n return promise;\n };\n return async (toolCall) => {\n switch (getMode()) {\n case \"allow-all\":\n return \"allow\";\n case \"deny-all\":\n return \"deny\";\n case \"read-only\":\n // Auto-approve read-only tools; route read-write/unknown tools to manual approval (matches CLI semantics).\n if (toolPermission(toolCall.payload.name) === \"r\") return \"allow\";\n return manual(toolCall);\n case \"always-ask\":\n default:\n return manual(toolCall);\n }\n };\n}\n","/**\n * Error capture within the message stream: LLM request\n * failures and tool execution failures are **both never expressed via throw** — core\n * converges them into the message stream (LLM and Environment handle errors\n * internally and never throw), so a try/catch can't catch a single one. This watcher\n * hooks onto SessionManager's drive, inspects messages one by one, and fishes them out\n * into error_records (source = `llm` / `environment`), matching usage-recorder's shape:\n * recognizes only a few payload types, no-op on the rest. **One instance per run/compact**\n * (its state wraps up accordingly, see close).\n *\n * LLM (source = `llm`): reads the status of `request_end` —\n * - `failed` → unexpected (not retryable: auth failure, invalid params, etc., needs a human);\n * - `timeout` / `malformed` → expected (the engine already reconnects and retries, part\n * of normal operation);\n * - `aborted` / `completed` are not recorded (the former is a user-initiated interrupt,\n * not an error).\n *\n * The message uses the real reason: `request_end` only carries status, and **the only\n * place core carries the actual failure-reason text is the `abort` event's reason**\n * (e.g. `llm request error: 401 …` / `malformed response failed after N retries`). So a\n * `request_end` failure is first held pending, not persisted immediately, and is\n * resolved at the next request boundary:\n * - Immediately followed by `abort` → use its reason as the message (the real reason);\n * - Immediately followed by `request_begin` (the engine is retrying) → no reason text\n * left to wait for, use the status text;\n * - Still unresolved when the run ends → close persists it as a fallback.\n * Exception: when reason is a user-interrupt message (`aborted …`), it's not trusted —\n * \"the user clicked stop during backoff\" isn't the reason for this timeout, so the\n * status text is used instead (that timeout is a genuine failure and is still recorded).\n * Pending state is bucketed by origin: subagent messages interleave with the parent\n * session's (even more so with parallel subagents), and mixing them up would misattribute.\n *\n * Environment (source = `environment`): reads `tool_call_output`'s stop_reason ∈\n * {failed, timeout} → expected (the error is fed back to the model, and the Agent\n * adjusts on its own; `aborted` is denial/interruption, not recorded).\n * `tool_call_output` only has tool_call_id, no tool name, so `tool_call_id → tool name`\n * is cached (tool_call always arrives before its output), and the tool name is written\n * into code (`tool_failed:exec_command`) — so the stats dashboard's \"most common error\n * code\" and the error table can show at a glance which tool failed.\n *\n * **Attribution (ctx) is recorded against the session that actually produced the error,\n * not always the parent Session**: a subagent's LLM failures and tool failures also flow\n * through this same stream (carrying origin); if we simply reused the parent ctx passed\n * in at construction, filtering errors by Agent would always show 0 for the child Agent\n * and an inflated count for the parent — both attribution stats and the troubleshooting\n * target would be wrong. So we recognize `session_meta` carrying origin (a subagent's\n * first message, always arriving before any of its failures), registering\n * `origin → {agentId, sessionId}` (agentId derived from the agent_state path, matching\n * SessionManager.registerChildSession's convention); at persist time we look up the\n * message's origin: a hit records the subagent, a miss (a main-session message, or\n * session_meta hasn't arrived yet) falls back to the parent ctx. projectId is always\n * taken from the parent — a subagent is necessarily in the same Project.\n */\nimport { isEventMessage, isModelMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport path from \"node:path\";\nimport type { OmniMessage, SessionMetaMessage, StopReason } from \"@prismshadow/penguin-core\";\nimport { MESSAGE_MAX } from \"./error-recorder.js\";\nimport type { ErrorContext, ErrorKind, ErrorSink } from \"./error-recorder.js\";\n\n/** Cap on the tool-name cache (bounded, to prevent unbounded growth over a long run; over the limit, evicts the oldest by registration order). */\nexport const TOOL_NAMES_MAX = 1000;\n\n/**\n * Cap on the subagent-identity cache (bounded, same reasoning as TOOL_NAMES_MAX: prevent\n * unbounded growth over a long run). The number of in-flight subagents is naturally\n * bounded by the subagent concurrency limit and falls far short of this value; over the\n * limit, evicts the oldest by registration order (those subagents have long since\n * settled, so even if a failure still arrives, it just falls back to the parent ctx —\n * i.e., the pre-fix behavior).\n */\nexport const ORIGIN_CTX_MAX = 200;\n\n/** Recorded LLM failure states (`aborted` / `completed` are not errors and aren't included here). */\ntype LlmFailure = \"failed\" | \"timeout\" | \"malformed\";\n\n/** LLM failure state → error code, classification, and fallback message (used when the abort reason isn't available). */\nconst LLM_FAILURES: Record<LlmFailure, { code: string; kind: ErrorKind; text: string }> = {\n failed: { code: \"llm_failed\", kind: \"unexpected\", text: \"LLM 请求失败(不可重试)。\" },\n timeout: { code: \"llm_timeout\", kind: \"expected\", text: \"LLM 请求超时(引擎重连重试)。\" },\n malformed: {\n code: \"llm_malformed\",\n kind: \"expected\",\n text: \"LLM 响应无法解析(引擎重连重试)。\",\n },\n};\n\n/** Recorded tool failure states (`aborted` = denial/interruption, not an error). */\ntype ToolFailure = \"failed\" | \"timeout\";\n\nfunction isLlmFailure(s: unknown): s is LlmFailure {\n return s === \"failed\" || s === \"timeout\" || s === \"malformed\";\n}\n\nfunction isToolFailure(s: unknown): s is ToolFailure {\n return s === \"failed\" || s === \"timeout\";\n}\n\n/** A user-interrupt abort message (core's `aborted by user` / `aborted during …`): not a failure reason. */\nfunction isUserAbortReason(reason: string): boolean {\n return /^aborted\\b/i.test(reason);\n}\n\n/** The session a message belongs to (last origin element; empty string for the main session) — both pending state and the tool-name cache are bucketed by it. */\nfunction originKey(msg: OmniMessage): string {\n const origin = msg.origin;\n return origin && origin.length > 0 ? origin[origin.length - 1]! : \"\";\n}\n\n/**\n * Take the **tail** of the tool output (not the head) as the message: core appends the\n * failure reason (`[tool error] …` / `[tool timeout: …]` / exit code) at the end of the\n * output, so truncating from the head would leave only a chunk of normal stdout and\n * drop the reason.\n */\nfunction toolFailureText(output: string): string {\n if (!output) return \"工具执行失败(无输出)。\";\n if (output.length <= MESSAGE_MAX) return output;\n return `…${output.slice(output.length - (MESSAGE_MAX - 1))}`;\n}\n\nexport class StreamErrorWatcher {\n /** LLM failures awaiting a real reason: origin → failure state (see file header; each session has at most one in-flight Request). */\n private readonly pending = new Map<string, LlmFailure>();\n /** Names of in-flight tool calls: `origin \\0 tool_call_id` → tool name (dequeued once output arrives). */\n private readonly toolNames = new Map<string, string>();\n /** Subagent identity: origin → that subagent's `{agentId, sessionId}` (see the file header's attribution section). */\n private readonly originCtx = new Map<string, { agentId: string; sessionId: string }>();\n\n constructor(\n private readonly errors: ErrorSink,\n private readonly ctx: ErrorContext,\n ) {}\n\n /** Consume one outgoing message; messages irrelevant to this watcher are a no-op. */\n observe(msg: OmniMessage): void {\n if (isSessionMeta(msg)) {\n this.registerOrigin(msg);\n return;\n }\n if (isModelMessage(msg)) {\n this.observeTool(msg);\n return;\n }\n if (isEventMessage(msg)) this.observeLlm(msg);\n }\n\n /** run/compact wrap-up: persist any still-pending failure (that never got its abort), and clear caches (prevents leaks). */\n close(): void {\n for (const key of [...this.pending.keys()]) this.flush(key);\n this.pending.clear();\n this.toolNames.clear();\n this.originCtx.clear();\n }\n\n // —— Attribution (see file header) ——\n\n /**\n * Register a subagent's identity: `session_meta` carrying origin is the subagent's\n * first message, always arriving before any of its failures. agentId is derived from\n * the absolute agent_state path (`<…>/<agentId>/agent_state`) — matching\n * SessionManager.registerChildSession's convention; not registered if the path is\n * malformed (that subagent's failures fall back to the parent ctx — better to\n * misattribute than write into a nonexistent agentId). The main session's session_meta\n * (no origin) is already the parent ctx and isn't registered.\n */\n private registerOrigin(msg: SessionMetaMessage): void {\n const key = originKey(msg);\n if (!key) return; // Main session\n const agentId = path.basename(path.dirname(msg.payload.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return;\n // Bounded (re-registering refreshes registration order; over the limit, evicts the oldest).\n this.originCtx.delete(key);\n this.originCtx.set(key, { agentId, sessionId: key });\n if (this.originCtx.size > ORIGIN_CTX_MAX) {\n const oldest = this.originCtx.keys().next().value;\n if (oldest !== undefined) this.originCtx.delete(oldest);\n }\n }\n\n /**\n * The attribution to persist for this origin: a registered subagent hit → record its\n * own Agent/Session; a miss (a main-session message, or session_meta hasn't arrived\n * yet) → fall back to the parent ctx passed at construction. projectId is always taken\n * from the parent (a subagent is necessarily in the same Project).\n */\n private ctxFor(key: string): ErrorContext {\n const child = this.originCtx.get(key);\n if (!child) return this.ctx;\n return { projectId: this.ctx.projectId, agentId: child.agentId, sessionId: child.sessionId };\n }\n\n // —— LLM ——\n\n private observeLlm(msg: OmniMessage): void {\n const p = msg.payload as { type?: string; status?: StopReason; reason?: string | null };\n const key = originKey(msg);\n if (p.type === \"request_end\") {\n this.flush(key); // Defensive: if a previous failure is still pending (normally resolved by request_begin), persist it first\n if (isLlmFailure(p.status)) this.pending.set(key, p.status);\n return;\n }\n // A new attempt begins (the engine is retrying): no reason text left to wait for the previous failure, persist using the status text.\n if (p.type === \"request_begin\") {\n this.flush(key);\n return;\n }\n // Interrupted/failed exit: reason is core's only failure-reason text.\n if (p.type === \"abort\") {\n this.flush(key, typeof p.reason === \"string\" ? p.reason : null);\n }\n }\n\n /**\n * Persist a pending LLM failure (no-op if none is pending); `reason` is the abort\n * message that arrived afterward. Pending state is already bucketed by origin, so\n * `key` is exactly \"the session that produced this failure\" — attribution is looked\n * up from it (see file header).\n */\n private flush(key: string, reason?: string | null): void {\n const status = this.pending.get(key);\n if (status === undefined) return;\n this.pending.delete(key);\n const spec = LLM_FAILURES[status];\n const trimmed = reason?.trim();\n // A user-interrupt message isn't a failure reason (see file header); fall back to the status text.\n const message = trimmed && !isUserAbortReason(trimmed) ? trimmed : spec.text;\n this.errors.record({\n source: \"llm\",\n err: message,\n ctx: this.ctxFor(key),\n code: spec.code,\n kind: spec.kind,\n });\n }\n\n // —— Environment (tool execution) ——\n\n private observeTool(msg: OmniMessage): void {\n const p = msg.payload as {\n type?: string;\n name?: string;\n output?: string;\n tool_call_id?: string;\n stop_reason?: StopReason;\n };\n if (typeof p.tool_call_id !== \"string\") return;\n const origin = originKey(msg); // The session that made this call (both attribution and the tool-name cache are bucketed by it)\n const key = `${origin}\\0${p.tool_call_id}`;\n\n if (p.type === \"tool_call\" && typeof p.name === \"string\") {\n // tool_call arrives before its output: record the tool name (bounded, re-registering refreshes registration order).\n this.toolNames.delete(key);\n this.toolNames.set(key, p.name);\n if (this.toolNames.size > TOOL_NAMES_MAX) {\n const oldest = this.toolNames.keys().next().value;\n if (oldest !== undefined) this.toolNames.delete(oldest);\n }\n return;\n }\n if (p.type !== \"tool_call_output\") return;\n\n const name = this.toolNames.get(key);\n this.toolNames.delete(key); // This call has settled: dequeue it, the cache only keeps in-flight calls\n if (!isToolFailure(p.stop_reason)) return; // completed / aborted (denial, user interrupt) are not errors\n this.errors.record({\n source: \"environment\",\n err: toolFailureText(p.output ?? \"\"),\n ctx: this.ctxFor(origin),\n // The tool name goes into code: so the stats dashboard's \"most common error code\" and table can show which tool failed.\n code: `tool_${p.stop_reason}:${name ?? \"unknown\"}`,\n kind: \"expected\",\n });\n }\n}\n","/**\n * Schedule runner: a Web server runtime component, active only\n * while the server runs. Loaded at server startup, then periodically reconciles by scanning the\n * `schedule/` directory.\n *\n * Key semantics:\n * - Intent vs state are separate: the file is declarative intent (never written back by the system);\n * run state lives in SQLite.\n * - No backfill for missed fires: any due time earlier than when this scheduler first learned of the\n * task (startup reconcile / first registration / start_at reset) is skipped — periodic tasks advance\n * last_slot straight to now, one-shot tasks are marked missed.\n * - Queue when busy: if the bound Session is running, queue (at most one per task; new due times during\n * the wait only advance, they don't stack), and send once it becomes idle.\n * - Bound Session deleted: record an error and mark invalid; editing the file re-activates it via reconcile.\n * - Deleting the file removes the task; reconcile also cleans up its SQLite run state and queue entry.\n */\nimport { createHash } from \"node:crypto\";\nimport { readdir } from \"node:fs/promises\";\nimport { agentsDir, userText } from \"@prismshadow/penguin-core\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SchedulesRepo, ScheduleStateRow } from \"../db/repos/schedules.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { latestSlotAt, slotInWindow } from \"./schedule-file.js\";\nimport { listScheduleFiles, readScheduleFile, validateScheduleModelRef } from \"./schedule-store.js\";\nimport type { ScheduleServerEvent } from \"../api/types.js\";\n\n/** Reconcile and fire-check interval (min period is 5m, so 30s granularity is plenty). */\nconst TICK_INTERVAL_MS = 30_000;\n\n/** Minimal dependency the scheduler needs from SessionManager (eases test doubles). */\nexport interface ScheduleTaskRunner {\n statusOf(sessionId: string): string;\n startTask(\n sessionId: string,\n input: ReturnType<typeof userText>[],\n ): Promise<{ sessionId: string }>;\n}\n\n/** Minimal dependency the scheduler needs from SessionService: new-Session mode (model ref passed through as a pair). */\nexport interface ScheduleSessionCreator {\n createSession(args: {\n projectId: string;\n agentId: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n source?: \"schedule\";\n }): Promise<{ sessionId: string }>;\n}\n\n/**\n * Trigger input = a `<scheduled_task>` origin block (task name and fire time) + the prompt body:\n * tells the model this was fired by a schedule; the frontend collapses the origin block into a\n * one-line schedule hint (Trace shows it verbatim).\n */\nexport function scheduledMessage(name: string, firedAt: string, prompt: string): string {\n return [\n \"<scheduled_task>\",\n \"This message was sent automatically by a scheduled task; its origin is listed below and the task prompt follows.\",\n `schedule: ${name}`,\n `fired_at: ${firedAt}`,\n \"</scheduled_task>\",\n \"\",\n prompt,\n ].join(\"\\n\");\n}\n\nexport interface SchedulerDeps {\n root: string;\n repo: SchedulesRepo;\n projects: ProjectsRepo;\n sessions: SessionsRepo;\n runner: ScheduleTaskRunner;\n sessionCreator: ScheduleSessionCreator;\n errors: ErrorSink;\n /** Fire and send are notified over the user-level event stream (app layer binds userChannelKey). */\n notify: (userId: string, event: ScheduleServerEvent) => void;\n now?: () => number;\n intervalMs?: number;\n}\n\n/** A queued fire (at most one per task). */\ninterface PendingFire {\n projectId: string;\n agentId: string;\n name: string;\n sessionId: string;\n}\n\n/** In-memory view of a registered task (definition + run state + queued flag). */\nexport interface ScheduleEntryView {\n def: ScheduleDefinition;\n state: ScheduleStateRow;\n queued: boolean;\n}\n\nexport class Scheduler {\n private readonly now: () => number;\n private readonly intervalMs: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n /** key = `${projectId}\u0000${agentId}\u0000${name}` */\n private readonly pending = new Map<string, PendingFire>();\n private ticking = false;\n\n constructor(private readonly deps: SchedulerDeps) {\n this.now = deps.now ?? (() => Date.now());\n this.intervalMs = deps.intervalMs ?? TICK_INTERVAL_MS;\n }\n\n /** Start: run one reconcile immediately (startup semantics: no backfill), then enter the periodic tick. */\n async start(): Promise<void> {\n await this.tickOnce();\n this.timer = setInterval(() => {\n void this.tickOnce();\n }, this.intervalMs);\n this.timer.unref?.();\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** One reconcile + fire pass (deterministic entry for tests and routes; concurrent calls run only one). */\n async tickOnce(): Promise<void> {\n if (this.ticking) return;\n this.ticking = true;\n try {\n for (const project of this.deps.projects.listAll()) {\n await this.reconcileProject(project.projectId, project.ownerUserId);\n }\n await this.drainQueue();\n } catch (err) {\n this.deps.errors.record({ source: \"schedule\", err, code: \"schedule_tick_failed\" });\n } finally {\n this.ticking = false;\n }\n }\n\n /** Immediate-effect entry after a route write: reconcile just one Agent and drain its queue. */\n async reconcileAgent(projectId: string, agentId: string): Promise<void> {\n const project = this.deps.projects.findById(projectId);\n if (!project) return;\n await this.reconcileOneAgent(projectId, agentId, project.ownerUserId);\n await this.drainQueue();\n }\n\n /** For route display: the task list after reconcile (including files that failed to parse). */\n async listAgent(\n projectId: string,\n agentId: string,\n ): Promise<{ entries: ScheduleEntryView[]; invalid: Array<{ name: string; error: string }> }> {\n const project = this.deps.projects.findById(projectId);\n const owner = project?.ownerUserId ?? null;\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n const entries: ScheduleEntryView[] = [];\n const invalid: Array<{ name: string; error: string }> = [];\n for (const file of files) {\n if (!file.parsed.ok) {\n invalid.push({ name: file.name, error: file.parsed.error });\n continue;\n }\n // An unresolvable model ref (no match / ambiguous) is treated like a parse failure: goes to invalidFiles, not scheduled.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, file.parsed.def);\n if (refError !== null) {\n invalid.push({ name: file.name, error: refError });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, owner, file.parsed.def, file.raw);\n entries.push({\n def: file.parsed.def,\n state,\n queued: this.pending.has(this.keyOf(projectId, agentId, file.name)),\n });\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n return { entries, invalid };\n }\n\n /** For routes: state cleanup after a task is deleted. */\n dropEntry(projectId: string, agentId: string, name: string): void {\n this.pending.delete(this.keyOf(projectId, agentId, name));\n this.deps.repo.delete(projectId, agentId, name);\n }\n\n // -------------------------------------------------------------------------\n\n private keyOf(projectId: string, agentId: string, name: string): string {\n return `${projectId}\u0000${agentId}\u0000${name}`;\n }\n\n private async reconcileProject(projectId: string, ownerUserId: string): Promise<void> {\n for (const agentId of await this.listAgentIds(projectId)) {\n await this.reconcileOneAgent(projectId, agentId, ownerUserId);\n }\n }\n\n /** Enumerate Agents under a Project: scheduling only cares about Agent dirs that exist on disk (no dir → no tasks). */\n private async listAgentIds(projectId: string): Promise<string[]> {\n try {\n const items = await readdir(agentsDir(this.deps.root, projectId), { withFileTypes: true });\n return items.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n return [];\n }\n }\n\n private async reconcileOneAgent(\n projectId: string,\n agentId: string,\n ownerUserId: string,\n ): Promise<void> {\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n for (const file of files) {\n if (!file.parsed.ok) {\n // Skip invalid files and record an error (the recorder dedups within a short window, so storms don't spam).\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`定时任务文件非法:${file.name}.toml——${file.parsed.error}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const def = file.parsed.def;\n // At reconcile time, check the model ref resolves (resolveModelRef semantics): unresolvable is\n // treated like an invalid file — skip scheduling and record an error (recorder dedups in a short\n // window); it recovers once the file/config is fixed.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, def);\n if (refError !== null) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`定时任务文件非法:${file.name}.toml——${refError}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, ownerUserId, def, file.raw);\n await this.evaluateEntry(projectId, agentId, def, state);\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n }\n\n /** Register (or sync) run state; set the no-backfill baseline only at first registration / start_at reset. */\n private registerEntry(\n projectId: string,\n agentId: string,\n ownerUserId: string | null,\n def: ScheduleDefinition,\n raw: string,\n ): ScheduleStateRow {\n const defHash = createHash(\"sha1\").update(raw).digest(\"hex\");\n const { row, fresh } = this.deps.repo.registerOrSync({\n projectId,\n agentId,\n name: def.name,\n startAtMs: def.startAtMs,\n defHash,\n creatorUserId: ownerUserId,\n });\n if (!fresh) return row;\n // Baseline: if the due time is already in the past at registration → no backfill (mark one-shot tasks\n // missed, let periodic tasks consume all past slots); if start_at is still in the future, do nothing and fire normally when due.\n const slot = latestSlotAt(def, this.now());\n if (slot === null) return row;\n if (def.periodMs === undefined) {\n this.deps.repo.markMissed(projectId, agentId, def.name);\n } else {\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n }\n return this.deps.repo.find(projectId, agentId, def.name) ?? row;\n }\n\n /** Clean up run state and queue entries for deleted files (deleting a file removes the task). */\n private cleanupMissing(projectId: string, agentId: string, presentNames: string[]): void {\n const removed = this.deps.repo.deleteMissing(projectId, agentId, presentNames);\n for (const name of removed) this.pending.delete(this.keyOf(projectId, agentId, name));\n }\n\n /** Fire decision: if enabled and within the window, consume new due times step by step. */\n private async evaluateEntry(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n if (!def.enabled || state.invalidReason !== null) return;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return;\n const nowMs = this.now();\n const slot = latestSlotAt(def, nowMs);\n if (slot === null || !slotInWindow(def, slot)) return;\n if (state.lastSlotMs !== null && slot <= state.lastSlotMs) return;\n // Consume this slot: never retry the same slot whether the send then succeeds, queues, or fails (the twin rule of no-backfill).\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n await this.dispatch(projectId, agentId, def, state);\n }\n\n /** Send one fire: queue if the bound Session is busy; in new-Session mode, create and send immediately. */\n private async dispatch(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n const key = this.keyOf(projectId, agentId, def.name);\n if (def.sessionId !== undefined) {\n const row = this.deps.sessions.findById(def.sessionId);\n if (!row || row.projectId !== projectId || row.agentId !== agentId) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`定时任务 ${def.name} 绑定的 Session 不存在:${def.sessionId}`),\n code: \"schedule_session_missing\",\n ctx: { projectId, agentId, sessionId: def.sessionId },\n });\n this.deps.repo.markInvalid(projectId, agentId, def.name, \"session_missing\");\n return;\n }\n if (this.deps.runner.statusOf(def.sessionId) !== \"idle\") {\n // Queue when busy: at most one per task; new slots during the wait are consumed but don't stack.\n if (!this.pending.has(key)) {\n this.pending.set(key, { projectId, agentId, name: def.name, sessionId: def.sessionId });\n this.notifyFor(state, {\n type: \"schedule_queued\",\n projectId,\n agentId,\n name: def.name,\n sessionId: def.sessionId,\n });\n }\n return;\n }\n await this.send(projectId, agentId, def, state, def.sessionId);\n return;\n }\n // New-Session mode: each fire opens a new session (optional workspace and paired model ref; same semantics as opening a session manually).\n try {\n const info = await this.deps.sessionCreator.createSession({\n projectId,\n agentId,\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n source: \"schedule\",\n });\n await this.send(projectId, agentId, def, state, info.sessionId);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_create_session_failed\",\n ctx: { projectId, agentId },\n });\n }\n }\n\n private async send(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n sessionId: string,\n ): Promise<void> {\n const firedAt = new Date(this.now()).toISOString();\n try {\n await this.deps.runner.startTask(sessionId, [\n userText(scheduledMessage(def.name, firedAt, def.prompt)),\n ]);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_send_failed\",\n ctx: { projectId, agentId, sessionId },\n });\n return;\n }\n this.deps.repo.markFired(projectId, agentId, def.name, firedAt, def.periodMs === undefined);\n this.notifyFor(state, {\n type: \"schedule_fired\",\n projectId,\n agentId,\n name: def.name,\n sessionId,\n });\n }\n\n /** Drain the queue: send once the target Session is idle; drop if the task is disabled/deleted/invalid. */\n private async drainQueue(): Promise<void> {\n for (const [key, fire] of [...this.pending]) {\n const state = this.deps.repo.find(fire.projectId, fire.agentId, fire.name);\n if (!state || state.invalidReason !== null) {\n this.pending.delete(key);\n continue;\n }\n const file = await readScheduleFile(this.deps.root, fire.projectId, fire.agentId, fire.name);\n if (!file || !file.parsed.ok || !file.parsed.def.enabled) {\n this.pending.delete(key);\n continue;\n }\n const row = this.deps.sessions.findById(fire.sessionId);\n if (!row) {\n this.pending.delete(key);\n this.deps.repo.markInvalid(fire.projectId, fire.agentId, fire.name, \"session_missing\");\n continue;\n }\n if (this.deps.runner.statusOf(fire.sessionId) !== \"idle\") continue;\n this.pending.delete(key);\n await this.send(fire.projectId, fire.agentId, file.parsed.def, state, fire.sessionId);\n }\n }\n\n /** Notify the creator (falls back to the Project owner at registration; silent if still absent). */\n private notifyFor(state: ScheduleStateRow, event: ScheduleServerEvent): void {\n const userId = state.creatorUserId;\n if (userId) this.deps.notify(userId, event);\n }\n}\n","/**\n * The **policy layer** for automatic Session title generation (conversation-page\n * extension).\n *\n * \"How to generate\" lives in the core SDK (`session.generateTitle`: an out-of-band\n * one-shot request on the session's own Model, no tools, thinking disabled, writes no\n * history/Trace); this module is only responsible for host-side policy:\n * - When to generate: after a Task completes and the DB row's title is still NULL\n * (i.e. after the first successful conversation);\n * - Persistence and notification: writes sessions.title and pushes a `session_title`\n * server event to the Session channel;\n * - Bookkeeping: the one-shot request's token consumption is converted to token_usage\n * and handed to usage-recorder for persistence;\n * - Silent failure (logged): the title stays NULL and naturally retries after the next\n * Task completes.\n */\nimport { emptyTokenCounts, sanitizeTitle, tokenUsage } from \"@prismshadow/penguin-core\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { RuntimeSession } from \"./session-manager.js\";\nimport type { UsageContext, UsageRecorder } from \"./usage-recorder.js\";\n\nexport interface TitleGeneratorDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n recorder: Pick<UsageRecorder, \"record\">;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n}\n\n/** Host-side parameters for one title-generation request. */\nexport interface TitleRequest {\n /** Fallback material for when the LLM fails or returns an empty result (cleaned and truncated from the first non-empty line). */\n fallbackText: string;\n /** Material override (for subagents — the material is the subagent's own conversation); defaults to the first Task's material self-collected by the core Session. */\n material?: { userText: string; assistantText: string };\n /** The channel to push the `session_title` event to; defaults to `ctx.sessionId`. A\n * subagent has no SSE channel of its own, so its title must reach the frontend via\n * the **parent Session's** channel (the list updates in place by sessionId). */\n notifyOn?: string;\n}\n\n/** session-manager's minimal dependency on the title generator (tests inject a fake implementation). */\nexport interface TitleNotifier {\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void;\n}\n\nexport class TitleGenerator implements TitleNotifier {\n private readonly inflight = new Set<string>();\n private readonly log: (line: string) => void;\n\n constructor(private readonly deps: TitleGeneratorDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n }\n\n /** Generate a title in the background (fire-and-forget) when conditions are met: the row exists, title is still NULL, and no generation is already in flight. */\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void {\n const row = this.deps.sessions.findById(ctx.sessionId);\n if (!row || row.title !== null) return;\n if (this.inflight.has(ctx.sessionId)) return;\n this.inflight.add(ctx.sessionId);\n void this.generate(ctx, session, req)\n .catch((err: unknown) => {\n this.log(`[title] 生成失败: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_failed\" });\n })\n .finally(() => {\n this.inflight.delete(ctx.sessionId);\n });\n }\n\n private async generate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): Promise<void> {\n let title: string | null = null;\n try {\n // Material defaults to what the core Session self-collects during run; it's only\n // overridden in scenarios like subagents where the material isn't on that Session.\n const res = await session.generateTitle(\n req.material ? { material: req.material } : undefined,\n );\n title = res.title;\n // The one-shot request's real consumption is metered as usual (converted to token_usage and handed to recorder, attributed to this Session).\n if (res.usage) {\n try {\n await this.deps.recorder.record(ctx, tokenUsage(emptyTokenCounts(), res.usage));\n } catch (err) {\n this.log(`[title] 用量落库失败: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({\n source: \"title\",\n err,\n ctx,\n code: \"title_usage_insert_failed\",\n });\n }\n }\n } catch (err) {\n // A model request error (rate limit / timeout / network, etc.) shouldn't leave the\n // title permanently missing: log it and fall through to the fallback.\n this.log(`[title] 模型请求失败: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_llm_failed\" });\n }\n // When the LLM produces no usable title (failure / empty result), truncate the fallback material's first line — this guarantees a title is always generated.\n const finalTitle = title ?? fallbackTitle(req.fallbackText);\n if (finalTitle === null) return;\n // There may already be a concurrent write during generation (e.g. a future manual rename): only persist if still NULL.\n const latest = this.deps.sessions.findById(ctx.sessionId);\n if (!latest || latest.title !== null) return;\n this.deps.sessions.updateTitle(ctx.sessionId, finalTitle);\n this.deps.channels\n .get(req.notifyOn ?? ctx.sessionId)\n .publish(\n { type: \"session_title\", sessionId: ctx.sessionId, title: finalTitle },\n \"server_event\",\n );\n }\n}\n\n/** Fallback title: take the material's first non-empty line, sanitize and truncate; if sanitizing empties it out (pure punctuation, etc.) fall back to the truncated original text; returns null if all-whitespace. */\nfunction fallbackTitle(text: string): string | null {\n const firstLine = text.split(\"\\n\").find((l) => l.trim().length > 0);\n if (!firstLine) return null;\n // sanitizeTitle strips a pure-punctuation line down to empty — in that case keep the truncated original text, guaranteeing \"a title is always obtained\".\n return sanitizeTitle(firstLine) ?? firstLine.trim().slice(0, 30);\n}\n","/**\n * Usage persistence.\n *\n * Consumes the Session output stream:\n * - A subagent's `session_meta` (carrying origin) → registers the mapping \"origin's\n * last session_id → (provider, model_id)\" (a subagent may use a different Model, so\n * cost is priced against the actual Model used);\n * - `token_usage` → inserts one usage_records row: the four token fields come from\n * `payload.request` (per-Request increment; subagents are recorded one row at a\n * time, with `session_id` attributed to their owning main Session). Only tokens are\n * persisted, not cost — pricing may be added later, and cost is computed on the fly\n * against current pricing when usage-service queries.\n * The attribution key is always the paired reference `(provider, model_id)` (the same\n * model_id name across different vendors is attributed separately).\n */\nimport { isEventMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\n\n/** Attribution context for one record (top-level Session scope). */\nexport interface UsageContext {\n projectId: string;\n agentId: string;\n /** Top-level Session id (the current actual id after self-heal). */\n sessionId: string;\n /** Vendor grouping for the top-level Session's model (paired with modelId; the fallback attribution when the origin mapping has no hit). */\n provider: string;\n /** Upstream model_id of the top-level Session (paired with provider). */\n modelId: string;\n}\n\n/** Cap on the subagent attribution mapping: over the limit, evicts the oldest by insertion order (an evicted entry falls back to the main Session's Model attribution). */\nexport const ORIGIN_MODELS_MAX = 1000;\n\nexport class UsageRecorder {\n /** Subagent model attribution mapping: origin's last session_id → paired reference (session_id is globally unique). */\n private readonly originModels = new Map<string, { provider: string; modelId: string }>();\n\n constructor(\n private readonly usage: UsageRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Consume one outgoing message; messages other than session_meta / token_usage are a no-op. */\n async record(ctx: UsageContext, msg: OmniMessage): Promise<void> {\n if (isSessionMeta(msg) && msg.origin && msg.origin.length > 0) {\n const originSessionId = msg.origin[msg.origin.length - 1]!;\n // Bounded mapping (avoids unbounded growth over a long-running process): re-inserting refreshes insertion order, over the limit evicts the oldest.\n this.originModels.delete(originSessionId);\n this.originModels.set(originSessionId, {\n provider: msg.payload.provider,\n modelId: msg.payload.model_id,\n });\n if (this.originModels.size > ORIGIN_MODELS_MAX) {\n const oldest = this.originModels.keys().next().value;\n if (oldest !== undefined) this.originModels.delete(oldest);\n }\n return;\n }\n if (!isEventMessage(msg)) return;\n const payload = msg.payload as {\n type?: string;\n request?: { cache_read: number; cache_write: number; output: number; total: number };\n status?: string;\n };\n\n const originSessionId =\n msg.origin && msg.origin.length > 0 ? msg.origin[msg.origin.length - 1]! : null;\n // Empty origin → main session's Model; otherwise look up the mapping, falling back to the main Session's Model (paired) on a miss.\n const ref =\n originSessionId === null\n ? { provider: ctx.provider, modelId: ctx.modelId }\n : (this.originModels.get(originSessionId) ?? {\n provider: ctx.provider,\n modelId: ctx.modelId,\n });\n const now = this.now();\n const base = {\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId: ctx.projectId,\n agentId: ctx.agentId,\n sessionId: ctx.sessionId,\n originSessionId,\n provider: ref.provider,\n modelId: ref.modelId,\n };\n\n if (payload.type === \"token_usage\" && payload.request) {\n // A successful request: persist along with tokens (status defaults to completed).\n const r = payload.request;\n this.usage.insert({\n ...base,\n cacheRead: r.cache_read,\n cacheWrite: r.cache_write,\n output: r.output,\n total: r.total,\n });\n return;\n }\n // A failed request (request_end and not completed, usually with no token_usage):\n // persist 0 tokens + status, feeding the \"model success rate\" stat; a successful\n // request is already counted once via the token_usage branch above, not repeated here.\n if (payload.type === \"request_end\" && payload.status && payload.status !== \"completed\") {\n this.usage.insert({\n ...base,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n status: payload.status,\n });\n }\n }\n}\n","/**\n * id rules: user_id / project_id / agent_id are semantic ids\n * chosen by their creator at creation time — starting with a lowercase letter,\n * containing only lowercase letters, digits, and underscores. The id doubles as the\n * directory name, so keeping it all-lowercase avoids directory name collisions on\n * case-insensitive filesystems (e.g. macOS) at the source.\n * The hyphen is a **reserved separator**: it only appears at the join point of a\n * non-admin project_id's \"<username>-<suffix>\" concatenation. Usernames never\n * contain a hyphen, so the first hyphen is the ownership boundary — no username can\n * ever be crafted to collide with another user's prefix, keeping namespaces\n * non-overlapping. session_id and temporary workspace ids are still generated by\n * the server (randomHex8).\n */\nimport { randomBytes } from \"node:crypto\";\n\n/** 8-character lowercase hex random string (used for server-generated ids like session_id / temporary workspace). */\nexport function randomHex8(): string {\n return randomBytes(4).toString(\"hex\");\n}\n\n/** General semantic id rule: starts with a lowercase letter, followed by lowercase letters, digits, or underscores only, 2-64 chars (no hyphen). */\nexport const SEMANTIC_ID_PATTERN = /^[a-z][a-z0-9_]{1,63}$/;\n\n/** Username tightens the general rule to 2-32 chars: leaves headroom for the default Project id `<username>-default_project`. */\nexport const USERNAME_PATTERN = /^[a-z][a-z0-9_]{1,31}$/;\n\n/** Suffix segment of a non-admin project_id (after `<username>-`): lowercase letters, digits, and underscores only. */\nexport const PROJECT_SUFFIX_PATTERN = /^[a-z0-9_]+$/;\n\n/** Upper bound on total project_id length (username <=32 + separator + suffix). */\nexport const PROJECT_ID_MAX_LENGTH = 64;\n\n/** Human-readable description of the rule (reused in error messages). */\nexport const SEMANTIC_ID_RULE = \"小写字母开头,仅小写字母、数字与下划线\";\n","/**\n * Admin user backend: user list / create / reset password / delete.\n *\n * - Create: username is the user_id (^[a-z][a-z0-9_-]{1,31}$); admin sets the initial\n * password, flagged with password_is_initial; a default Project `proj-<username>` is\n * auto-created, rolling back the user row on failure.\n * - Reset password: also flags the password as initial and clears all of the user's\n * login sessions (forcing re-login).\n * - Delete: the built-in admin cannot be deleted; Projects owned by the user are\n * deleted along with it (including data directories), with sessions/memberships/UI\n * preferences cascade-deleted via foreign keys.\n */\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { MIN_PASSWORD_LENGTH, toUserInfo } from \"../auth/service.js\";\nimport { hashPassword } from \"../auth/password.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { SEMANTIC_ID_RULE, USERNAME_PATTERN } from \"./ids.js\";\nimport type { ProjectService } from \"./project-service.js\";\n\nexport interface AdminServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n projects: ProjectsRepo;\n projectService: ProjectService;\n now?: () => Date;\n}\n\nexport class AdminService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AdminServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n listUsers(): UserInfo[] {\n return this.deps.users.list().map(toUserInfo);\n }\n\n async createUser(userId: string, password: string): Promise<UserInfo> {\n if (!USERNAME_PATTERN.test(userId)) {\n throw new HttpError(400, \"invalid_user_id\", `用户名须为 2~32 位:${SEMANTIC_ID_RULE}。`);\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"密码至少 8 个字符。\");\n }\n if (this.deps.users.findById(userId)) {\n throw new HttpError(409, \"user_exists\", `用户已存在:${userId}。`);\n }\n const user: UserRow = {\n userId,\n passwordHash: await hashPassword(password),\n isAdmin: false,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.projectService.provisionInitialProject(user, false);\n } catch (err) {\n // Compensation: roll back the user row if default Project creation fails (e.g. proj-<username> already taken).\n this.deps.users.delete(user.userId);\n throw err;\n }\n return toUserInfo(user);\n }\n\n /** Reset another user's password: flags it as initial and clears all their sessions (prompts a password change on next login). */\n async resetPassword(userId: string, password: string): Promise<void> {\n if (!this.deps.users.findById(userId)) {\n throw new HttpError(404, \"user_not_found\", `用户不存在:${userId}。`);\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"密码至少 8 个字符。\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(password), true);\n this.deps.authSessions.deleteByUser(userId);\n }\n\n /** Delete user: the built-in admin cannot be deleted; owned Projects (including data directories) are deleted along with it. */\n async deleteUser(userId: string): Promise<void> {\n const target = this.deps.users.findById(userId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `用户不存在:${userId}。`);\n }\n if (target.isAdmin) {\n throw new HttpError(409, \"cannot_delete_admin\", \"内置管理员不能删除。\");\n }\n for (const project of this.deps.projects.listByOwner(userId)) {\n await this.deps.projectService.destroyProject(project.projectId);\n }\n this.deps.users.delete(userId); // auth_sessions / project_members / ui_prefs cascade-deleted\n }\n}\n","/**\n * Agent config read/write (config is an editable file).\n *\n * system_config.yaml is edited via yaml's `parseDocument`: only the keys provided in\n * the request are updated, the rest of the file (including comments) is preserved\n * as-is; AGENTS.md is overwritten in full.\n * The vault (agent_state/.vault.toml) is read/written via core's loadAgentVault/saveAgentVault;\n * plaintext values only ever hit disk, and are always masked in responses.\n */\nimport fs from \"node:fs/promises\";\nimport { parseDocument, parse as parseYaml } from \"yaml\";\nimport {\n agentsMdPath,\n agentStateDir,\n agentStateVersion,\n VAULT_VALUE_MAX_LENGTH,\n isValidVaultKey,\n loadAgentVault,\n saveAgentVault,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type {\n MCPServerConfig,\n ThinkingLevelName,\n ToolDefinitionConfig,\n} from \"@prismshadow/penguin-core\";\nimport type {\n AgentConfigDto,\n AgentConfigUpdateRequest,\n AgentModelConfigDto,\n AgentCompactionConfigDto,\n VaultEntryInfo,\n VaultResponse,\n VaultUpdateRequest,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest, optionalEnum, optionalNumber, optionalString } from \"../http/validate.js\";\nimport { maskApiKey } from \"./project-config-service.js\";\n\nconst THINKING_LEVELS: readonly ThinkingLevelName[] = [\"none\", \"low\", \"medium\", \"high\", \"xhigh\"];\nconst COMPACTION_MODES = [\"summarize\", \"discard\"] as const;\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nexport interface AgentConfigView {\n agentsMd: string;\n systemConfigYaml: string;\n config: AgentConfigDto;\n stateDir: string;\n}\n\nexport class AgentConfigService {\n constructor(private readonly root: string) {}\n\n /** Whether the Agent exists (determined by the presence of system_config.yaml, matching the CLI's convention). */\n async exists(projectId: string, agentId: string): Promise<boolean> {\n try {\n await fs.access(systemConfigPath(this.root, projectId, agentId));\n return true;\n } catch {\n return false;\n }\n }\n\n async requireExists(projectId: string, agentId: string): Promise<void> {\n if (!(await this.exists(projectId, agentId))) {\n throw new HttpError(404, \"agent_not_found\", \"Agent 不存在。\");\n }\n }\n\n /**\n * Read list-card metadata: name / description + tool count (sum of tools.builtin\n * and tools.mcpServers entries; MCP counted per server). Silently falls back to\n * empty / 0 if the file is corrupt.\n */\n async readCardMeta(\n projectId: string,\n agentId: string,\n ): Promise<{ name?: string; description?: string; toolCount: number; version: number }> {\n try {\n const raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n const parsed = asRecord(parseYaml(raw));\n const tools = asRecord(parsed.tools);\n const countOf = (v: unknown): number => (Array.isArray(v) ? v.length : 0);\n return {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n toolCount: countOf(tools.builtin) + countOf(tools.mcpServers),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n };\n } catch {\n return { toolCount: 0, version: 1 };\n }\n }\n\n /** Structured config view (matching the edit form's shape) + raw text + AGENTS.md + State path. */\n async getConfig(projectId: string, agentId: string): Promise<AgentConfigView> {\n await this.requireExists(projectId, agentId);\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const systemConfigYaml = await fs.readFile(yamlPath, \"utf8\");\n const parsed = asRecord(parseYaml(systemConfigYaml));\n const model = asRecord(parsed.model);\n const compaction = asRecord(parsed.compaction);\n const tools = asRecord(parsed.tools);\n\n let agentsMd = \"\";\n try {\n agentsMd = await fs.readFile(agentsMdPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n // Treat a missing AGENTS.md as an empty file (it normally exists after initialization).\n }\n\n const modelDto: AgentModelConfigDto = {\n ...(typeof model.max_tokens === \"number\" ? { maxTokens: model.max_tokens } : {}),\n ...(typeof model.thinking_level === \"string\"\n ? { thinkingLevel: model.thinking_level as ThinkingLevelName }\n : {}),\n ...(typeof model.timeoutMs === \"number\" ? { timeoutMs: model.timeoutMs } : {}),\n };\n const compactionDto: AgentCompactionConfigDto = {\n ...(typeof compaction.max_context_length === \"number\"\n ? { maxContextLength: compaction.max_context_length }\n : {}),\n ...(typeof compaction.max_session_turns === \"number\"\n ? { maxSessionTurns: compaction.max_session_turns }\n : {}),\n ...(compaction.mode === \"summarize\" || compaction.mode === \"discard\"\n ? { mode: compaction.mode }\n : {}),\n ...(typeof compaction.prompt === \"string\" ? { prompt: compaction.prompt } : {}),\n };\n const config: AgentConfigDto = {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n systemPrompt: typeof parsed.system_prompt === \"string\" ? parsed.system_prompt : \"\",\n ...(typeof parsed.max_turns === \"number\" ? { maxTurns: parsed.max_turns } : {}),\n ...(Object.keys(modelDto).length > 0 ? { model: modelDto } : {}),\n ...(Object.keys(compactionDto).length > 0 ? { compaction: compactionDto } : {}),\n toolsBuiltin: Array.isArray(tools.builtin) ? (tools.builtin as ToolDefinitionConfig[]) : [],\n mcpServers: Array.isArray(tools.mcpServers) ? (tools.mcpServers as MCPServerConfig[]) : [],\n };\n return {\n agentsMd,\n systemConfigYaml,\n config,\n stateDir: agentStateDir(this.root, projectId, agentId),\n };\n }\n\n /**\n * PUT accepts any subset: only the provided keys are updated (parseDocument\n * preserves comments and untouched content); agentsMd is overwritten in full.\n * Numeric validation: >0 or -1; thinkingLevel / mode are validated as enums.\n */\n async updateConfig(\n projectId: string,\n agentId: string,\n req: AgentConfigUpdateRequest,\n ): Promise<void> {\n await this.requireExists(projectId, agentId);\n // Finish all config validation and document changes before writing to disk\n // (if validation fails, AGENTS.md is not written either, avoiding a partial update).\n if (req.config !== undefined) {\n await this.applyConfigUpdate(projectId, agentId, req.config);\n }\n if (req.agentsMd !== undefined) {\n await fs.writeFile(agentsMdPath(this.root, projectId, agentId), req.agentsMd, \"utf8\");\n }\n }\n\n private async applyConfigUpdate(\n projectId: string,\n agentId: string,\n config: NonNullable<AgentConfigUpdateRequest[\"config\"]>,\n ): Promise<void> {\n const cfg = config as unknown as Record<string, unknown>;\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const doc = parseDocument(await fs.readFile(yamlPath, \"utf8\"));\n\n const setIfProvided = (path: string[], value: unknown): void => {\n if (value !== undefined) doc.setIn(path, value);\n };\n\n setIfProvided([\"name\"], optionalString(cfg, \"name\", { maxLen: 100, label: \"name\" }));\n setIfProvided(\n [\"description\"],\n optionalString(cfg, \"description\", { maxLen: 2000, label: \"description\" }),\n );\n setIfProvided(\n [\"system_prompt\"],\n optionalString(cfg, \"systemPrompt\", { label: \"systemPrompt\" }),\n );\n setIfProvided(\n [\"max_turns\"],\n optionalNumber(cfg, \"maxTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n\n if (cfg.model !== undefined) {\n const model = asRecord(cfg.model);\n setIfProvided(\n [\"model\", \"max_tokens\"],\n optionalNumber(model, \"maxTokens\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"model\", \"thinking_level\"],\n optionalEnum(model, \"thinkingLevel\", THINKING_LEVELS),\n );\n setIfProvided(\n [\"model\", \"timeoutMs\"],\n optionalNumber(model, \"timeoutMs\", { integer: true, positiveOrMinusOne: true }),\n );\n }\n if (cfg.compaction !== undefined) {\n const compaction = asRecord(cfg.compaction);\n setIfProvided(\n [\"compaction\", \"max_context_length\"],\n optionalNumber(compaction, \"maxContextLength\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"compaction\", \"max_session_turns\"],\n optionalNumber(compaction, \"maxSessionTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided([\"compaction\", \"mode\"], optionalEnum(compaction, \"mode\", COMPACTION_MODES));\n setIfProvided([\"compaction\", \"prompt\"], optionalString(compaction, \"prompt\"));\n }\n if (cfg.toolsBuiltin !== undefined) {\n doc.setIn([\"tools\", \"builtin\"], validateToolsBuiltin(cfg.toolsBuiltin));\n }\n if (cfg.mcpServers !== undefined) {\n doc.setIn([\"tools\", \"mcpServers\"], validateMcpServers(cfg.mcpServers));\n }\n\n await fs.writeFile(yamlPath, doc.toString(), \"utf8\");\n }\n\n /** Read the Agent vault (agent_state/.vault.toml): values are always masked, plaintext is never sent to the client. */\n async getVault(projectId: string, agentId: string): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const vault = await loadAgentVault(this.root, projectId, agentId);\n const entries: VaultEntryInfo[] = Object.entries(vault).map(([key, value]) => ({\n key,\n valueMasked: maskApiKey(value),\n }));\n return { entries };\n }\n\n /**\n * PUT replaces the whole vault table (same semantics as models): keys absent from\n * the body are deleted; omitting value keeps the existing value (a new key must\n * provide a value). Key names are validated against shell environment variable\n * naming rules (same rule as core); deleting everything removes the whole\n * .vault.toml file.\n */\n async updateVault(\n projectId: string,\n agentId: string,\n req: VaultUpdateRequest,\n ): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const prev = await loadAgentVault(this.root, projectId, agentId);\n\n const seen = new Set<string>();\n const nextVault: Record<string, string> = {};\n for (const entry of req.entries) {\n if (!isValidVaultKey(entry.key)) {\n throw badRequest(\n `vault 键名不合法:${entry.key}(仅字母、数字与下划线,且不能以数字开头)。`,\n );\n }\n if (seen.has(entry.key)) {\n throw badRequest(`entries 中存在重复的键名:${entry.key}。`);\n }\n seen.add(entry.key);\n const prevValue = prev[entry.key];\n if (entry.value !== undefined) {\n // Values are injected into the child process environment: an oversized value would\n // make exec spawn fail (E2BIG), so we reject it on write (same limit as core).\n if (entry.value.length > VAULT_VALUE_MAX_LENGTH) {\n throw badRequest(`vault 值过长:${entry.key}(上限 ${VAULT_VALUE_MAX_LENGTH} 字符)。`);\n }\n nextVault[entry.key] = entry.value;\n } else if (prevValue !== undefined) {\n nextVault[entry.key] = prevValue;\n } else {\n throw badRequest(`新增键 ${entry.key} 必须提供 value。`);\n }\n }\n\n await saveAgentVault(this.root, projectId, agentId, nextVault);\n return this.getVault(projectId, agentId);\n }\n}\n\nfunction validateToolsBuiltin(value: unknown): ToolDefinitionConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"toolsBuiltin 必须是数组。\");\n return value.map((item, i) => {\n const t = asRecord(item);\n if (typeof t.name !== \"string\" || t.name.length === 0) {\n throw badRequest(`toolsBuiltin[${i}].name 必须是非空字符串。`);\n }\n if (typeof t.description !== \"string\") {\n throw badRequest(`toolsBuiltin[${i}].description 必须是字符串。`);\n }\n if (t.permission !== undefined && t.permission !== \"r\" && t.permission !== \"rw\") {\n throw badRequest(`toolsBuiltin[${i}].permission 必须是 r / rw 之一。`);\n }\n if (t.forModel !== undefined && t.forModel !== \"vision\" && t.forModel !== \"text-only\") {\n throw badRequest(`toolsBuiltin[${i}].forModel 必须是 vision / text-only 之一。`);\n }\n optionalNumber(t, \"timeoutMs\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].timeoutMs`,\n });\n optionalNumber(t, \"maxOutputLength\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].maxOutputLength`,\n });\n return t as unknown as ToolDefinitionConfig;\n });\n}\n\nfunction validateMcpServers(value: unknown): MCPServerConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"mcpServers 必须是数组。\");\n return value.map((item, i) => {\n const s = asRecord(item);\n if (typeof s.name !== \"string\" || s.name.length === 0) {\n throw badRequest(`mcpServers[${i}].name 必须是非空字符串。`);\n }\n if (s.config === null || typeof s.config !== \"object\" || Array.isArray(s.config)) {\n throw badRequest(`mcpServers[${i}].config 必须是对象。`);\n }\n return s as unknown as MCPServerConfig;\n });\n}\n","/**\n * `.project_config.toml` read/write (single hidden config file).\n *\n * Doesn't reuse core's loadProjectConfig/saveProjectConfig (they only keep known\n * fields): reads and writes the complete object directly via smol-toml, preserving\n * extension fields like `name`. credential (api_key / base_url / created_at) is\n * **inlined on the model entry** — there's no longer a supplementary section or\n * secrets file; since the file contains secrets, it's always written with mode\n * 0600. Plaintext only ever hits disk, and is always masked in responses.\n *\n * Model references are **fully split into separate fields**: an entry is\n * stored as two independent fields, `provider` and `model_id`; the `(provider,\n * model_id)` pair is the entry's unique key. `model_id` is the upstream request id,\n * sent to AgentHub verbatim — string concatenation like `<provider>/<id>` is\n * forbidden everywhere in the pipeline. `default_model` / `vision_model` are `{\n * provider, model_id }` paired references (TOML tables).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport {\n GenerativeModel,\n catalogEntryFor,\n defaultProjectConfig,\n projectConfigPath,\n renderProjectConfigToml,\n resolveModelEnv,\n userText,\n} from \"@prismshadow/penguin-core\";\nimport type { ModelRef } from \"@prismshadow/penguin-core\";\nimport type {\n ModelInfo,\n ModelPricingDto,\n ModelRefDto,\n ModelsResponse,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelTestResponse,\n} from \"../api/types.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { PricingRates } from \"./usage-service.js\";\n\ntype RawTable = Record<string, unknown>;\n\n/**\n * API key masking: length <=12 -> `***`, otherwise `first4…last4`; plaintext is\n * never sent to the client. The 12-char threshold: `first4…last4` exposes 8\n * characters, which for a 9-12 character short secret would leak more than half of\n * it, so those are masked in full instead.\n */\nexport function maskApiKey(key: string): string {\n if (key.length <= 12) return \"***\";\n return `${key.slice(0, 4)}…${key.slice(-4)}`;\n}\n\nfunction asTable(v: unknown): RawTable {\n return v !== null && typeof v === \"object\" && !Array.isArray(v) ? (v as RawTable) : {};\n}\n\nfunction asArray(v: unknown): RawTable[] {\n return Array.isArray(v) ? v.map(asTable) : [];\n}\n\nfunction optNum(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction optStr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Leniently reads a paired reference table (default_model / vision_model); returns undefined on a shape mismatch (including the old string format). */\nfunction optRef(v: unknown): ModelRef | undefined {\n const t = asTable(v);\n const provider = optStr(t.provider);\n const modelId = optStr(t.model_id);\n return provider !== undefined && modelId !== undefined\n ? { provider, model_id: modelId }\n : undefined;\n}\n\n/** Whether an entry matches a paired reference (the entry's provider / model_id fields must be strings). */\nfunction entryMatches(m: RawTable, provider: string, modelId: string): boolean {\n return m.provider === provider && m.model_id === modelId;\n}\n\n/** In-process Map/Set key for a paired reference (\\0-separated to avoid concatenation ambiguity; never persisted, not an id format). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\n/** Display form of a paired reference (for error messages; display only, not a storage format). */\nfunction showRef(provider: string, modelId: string): string {\n return `(provider=${provider}, model_id=${modelId})`;\n}\n\nexport class ProjectConfigService {\n constructor(private readonly root: string) {}\n\n private filePath(projectId: string): string {\n return projectConfigPath(this.root, projectId);\n }\n\n /** Reads the raw TOML object; returns an empty object if the file doesn't exist (does not write to disk). */\n async readRaw(projectId: string): Promise<RawTable> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath(projectId), \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw err;\n }\n return asTable(parseToml(raw));\n }\n\n /**\n * Writes the whole object to disk: the file inlines secrets like api_key, always\n * written with mode 0600 (the `mode` option only applies at creation time, so\n * chmod is used to enforce it on existing files too — matching core's\n * saveProjectConfig behavior).\n */\n async writeRaw(projectId: string, data: RawTable): Promise<void> {\n const file = this.filePath(projectId);\n await fs.mkdir(path.dirname(file), { recursive: true });\n // Rendering goes through core's single writer: paired references become inline\n // tables, models is placed last — matching the CLI's output format exactly\n // (the same file should never have two formats).\n await fs.writeFile(file, renderProjectConfigToml(data), { encoding: \"utf8\", mode: 0o600 });\n await fs.chmod(file, 0o600);\n }\n\n /**\n * Initial config for a newly created Project: display name + preset built-in\n * model catalog (the default model and all preset entries, sourced from the same\n * core defaultProjectConfig; a gateway model's base_url is already inlined on the\n * entry, with no key); users only need to fill in an API key as needed (leave it\n * blank to fall back to the provider's environment variable).\n */\n async writeInitialConfig(projectId: string, name: string): Promise<void> {\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n name,\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /**\n * Backfills preset models (for onboarding an existing Project, e.g. the\n * `default_project` shared with the CLI when the first user is onboarded — its\n * directory already existed and never went through `writeInitialConfig`, so it\n * previously had no models and no default model).\n *\n * **Only backfills when there are no models at all**: a Project that already has\n * models configured (via the CLI or edited by the user) is left as-is, and its\n * other fields (name, etc.) are preserved too — existing config is never\n * overwritten.\n */\n async ensurePresetModels(projectId: string): Promise<void> {\n const raw = await this.readRaw(projectId);\n if (asArray(raw.models).length > 0) return;\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n ...raw,\n // Also reset to the preset default_model if the existing one points at a now-deleted model, to keep the default model valid.\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /** Project display name (the toml's name; returns undefined if unset, the frontend falls back to displaying the id). */\n async getName(projectId: string): Promise<string | undefined> {\n const raw = await this.readRaw(projectId);\n return typeof raw.name === \"string\" ? raw.name : undefined;\n }\n\n /** Paired reference of the default Model; returns undefined if unconfigured (or in the old string format). */\n async getDefaultModelRef(projectId: string): Promise<ModelRef | undefined> {\n const raw = await this.readRaw(projectId);\n return optRef(raw.default_model);\n }\n\n /** Pricing lookup for usage-recorder: the current pricing for this paired reference (undefined if none -> cost is NULL). */\n async getPricing(\n projectId: string,\n provider: string,\n modelId: string,\n ): Promise<PricingRates | undefined> {\n const raw = await this.readRaw(projectId);\n const entry = asArray(raw.models).find((m) => entryMatches(m, provider, modelId));\n const pricing = entry ? asTable(entry.pricing) : {};\n const cacheRead = optNum(pricing.cache_read);\n const cacheWrite = optNum(pricing.cache_write);\n const output = optNum(pricing.output);\n if (cacheRead === undefined && cacheWrite === undefined && output === undefined) {\n return undefined;\n }\n return { cacheRead: cacheRead ?? 0, cacheWrite: cacheWrite ?? 0, output: output ?? 0 };\n }\n\n /**\n * Model connectivity test: the model reference `(provider, modelId)` is submitted\n * as a pair in the request body; sends one minimal request using that model's\n * config (optionally overridden with an unsaved apiKey / baseUrl) — no tools, no\n * system prompt, thinking disabled, a tiny output cap, 20s timeout — just to see\n * whether it completes normally. The model id sent to AgentHub is `modelId`\n * itself (the upstream id verbatim; client_type inference follows it).\n *\n * Never throws: the LLM layer collapses auth/parameter/network errors into an\n * `LLMOutcome`, which is translated here into ok / message. Consumes very few\n * Tokens (single-digit output), and writes no Trace and records no usage.\n */\n async testModel(projectId: string, req: ModelTestRequest): Promise<ModelTestResponse> {\n const raw = await this.readRaw(projectId);\n // Testable even if the model isn't in the config yet (validate before saving when adding a custom model): in that case all parameters come from the request body.\n const entry = asArray(raw.models).find((m) => entryMatches(m, req.provider, req.modelId)) ?? {};\n // Always tests against the **current form draft**: checking \"clear\" means the saved key is not fallen back to; an explicit null base URL is treated as cleared.\n const savedKey = optStr(entry.api_key);\n const apiKey = req.clearApiKey ? undefined : (req.apiKey ?? savedKey);\n const savedBaseUrl = optStr(entry.base_url);\n const baseUrl = req.baseUrl === null ? undefined : (req.baseUrl ?? savedBaseUrl);\n const clientType = req.clientType ?? optStr(entry.client_type);\n\n const startedAt = Date.now();\n try {\n // Construction must be inside the try block: the underlying provider SDK can\n // throw during **client construction** itself when a credential is missing\n // (models on the OpenAI protocol need apiKey/OPENAI_API_KEY) — the whole point\n // of a connectivity test is to collapse that kind of failure into\n // `{ ok:false }`; if construction were outside the try, a missing-key test\n // would bubble up as a 500.\n const llm = new GenerativeModel({\n modelId: req.modelId,\n ...(apiKey ? { apiKey } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(clientType ? { clientType } : {}),\n tools: [],\n thinkingLevel: \"none\",\n maxTokens: 16,\n requestTimeoutMs: 20_000,\n });\n const gen = llm.streamGenerate({ newMessages: [userText(\"ping\")] });\n for (;;) {\n const step = await gen.next();\n if (step.done) {\n const outcome = step.value;\n if (outcome.status === \"completed\")\n return { ok: true, latencyMs: Date.now() - startedAt };\n const detail = \"message\" in outcome && outcome.message ? outcome.message : outcome.status;\n return { ok: false, message: String(detail).slice(0, 300) };\n }\n }\n } catch (err) {\n // Defensive: an unexpected exception during construction/iteration (the LLM layer promises not to throw; this is a fallback).\n return {\n ok: false,\n message: (err instanceof Error ? err.message : String(err)).slice(0, 300),\n };\n }\n }\n\n /**\n * GET models view: masks credential (inline fields), flags the default Model;\n * the group is the entry's `provider` field, looked up in the built-in catalog by\n * the `(provider, model_id)` pair to fill in displayName / envKey (entries outside\n * the catalog are treated as custom models: envKey only has a fallback for the\n * openai protocol). vision follows the TOML annotation when present, otherwise\n * falls back to the catalog annotation (if neither exists, the field is omitted =\n * supported by default).\n */\n async getModels(projectId: string): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const defaultRef = optRef(raw.default_model);\n const visionRef = optRef(raw.vision_model);\n const models: ModelInfo[] = asArray(raw.models)\n // An entry is valid only if both provider and model_id are strings (an entry in the old concatenated format lacks provider and is ignored).\n .filter((m) => typeof m.provider === \"string\" && typeof m.model_id === \"string\")\n .map((m) => {\n const provider = m.provider as string;\n const modelId = m.model_id as string;\n const pricing = asTable(m.pricing);\n const pricingDto: ModelPricingDto | undefined =\n optNum(pricing.cache_read) !== undefined ||\n optNum(pricing.cache_write) !== undefined ||\n optNum(pricing.output) !== undefined\n ? {\n cacheRead: optNum(pricing.cache_read) ?? 0,\n cacheWrite: optNum(pricing.cache_write) ?? 0,\n output: optNum(pricing.output) ?? 0,\n }\n : undefined;\n const clientType = optStr(m.client_type);\n const cat = catalogEntryFor(provider, modelId);\n // The env fallback is reported as-is: follows the same rule as\n // AgentHub routing — an explicit client_type takes priority (the openai\n // protocol reads OPENAI_*, independent of the group), otherwise it's\n // auto-routed to a provider client based on model_id; an id that can't be\n // routed has no fallback (no envKey, and AgentHub will reject that id).\n const envKey = resolveModelEnv(modelId, clientType)?.envKey;\n const vision = typeof m.vision === \"boolean\" ? m.vision : cat?.supportsVision;\n // Display name: the explicit TOML field (user-edited) takes priority, then the built-in catalog.\n const displayName = optStr(m.display_name) ?? cat?.displayName;\n // credential is inlined on the entry: a credential block is emitted if either api_key or base_url is present.\n const apiKey = optStr(m.api_key);\n const credBaseUrl = optStr(m.base_url);\n const createdAt = optStr(m.created_at);\n const info: ModelInfo = {\n provider,\n modelId,\n ...(displayName !== undefined ? { displayName } : {}),\n isDefault:\n defaultRef !== undefined &&\n defaultRef.provider === provider &&\n defaultRef.model_id === modelId,\n ...(optNum(m.context_window) !== undefined\n ? { contextWindow: optNum(m.context_window)! }\n : {}),\n ...(clientType ? { clientType } : {}),\n ...(vision !== undefined ? { vision } : {}),\n ...(envKey ? { envKey } : {}),\n ...(pricingDto ? { pricing: pricingDto } : {}),\n ...(apiKey !== undefined || credBaseUrl !== undefined\n ? {\n credential: {\n ...(apiKey !== undefined ? { apiKeyMasked: maskApiKey(apiKey) } : {}),\n ...(credBaseUrl !== undefined ? { baseUrl: credBaseUrl } : {}),\n ...(createdAt !== undefined ? { createdAt } : {}),\n },\n }\n : {}),\n };\n return info;\n });\n const toDto = (ref: ModelRef): ModelRefDto => ({\n provider: ref.provider,\n modelId: ref.model_id,\n });\n return {\n ...(defaultRef !== undefined ? { defaultModel: toDto(defaultRef) } : {}),\n ...(visionRef !== undefined ? { visionModel: toDto(visionRef) } : {}),\n models,\n };\n }\n\n /**\n * PUT replaces the whole models table: key =\n * `(provider, modelId)`; model entries that no longer appear are deleted along\n * with their inline credential; omitting apiKey keeps the existing value,\n * providing one overwrites it and records created_at, clearApiKey clears it;\n * baseUrl null clears it / omitted keeps it. A key change (either the group or\n * the upstream id changes) is migrated as a pair via `renamedFrom`: credential and\n * unknown fields migrate along with the base entry, and default/vision pointers\n * follow. Other extension fields in the toml (name, etc.) are preserved.\n */\n async updateModels(projectId: string, req: ModelsUpdateRequest): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const prevModels = asArray(raw.models);\n\n const seen = new Set<string>();\n const nextModels: RawTable[] = [];\n // Rename mapping (old reference key -> new reference): default model / vision model pointers follow a key change instead of being lost on a full table replacement.\n const renamed = new Map<string, ModelRefDto>();\n for (const entry of req.models) {\n const key = refKey(entry.provider, entry.modelId);\n if (seen.has(key)) {\n throw badRequest(\n `models 中存在重复的模型引用:${showRef(entry.provider, entry.modelId)}。`,\n );\n }\n seen.add(key);\n if (\n entry.renamedFrom !== undefined &&\n !(\n entry.renamedFrom.provider === entry.provider &&\n entry.renamedFrom.modelId === entry.modelId\n )\n ) {\n renamed.set(refKey(entry.renamedFrom.provider, entry.renamedFrom.modelId), {\n provider: entry.provider,\n modelId: entry.modelId,\n });\n }\n\n // Model entry: uses the old entry (the entry for the original reference when\n // the key changed) as the base, preserving unknown fields and inline\n // credential; known fields are replaced wholesale per the request (omitted\n // means removed).\n const prevRef = entry.renamedFrom ?? { provider: entry.provider, modelId: entry.modelId };\n const prev = prevModels.find((m) => entryMatches(m, prevRef.provider, prevRef.modelId)) ?? {};\n const next: RawTable = { ...prev, provider: entry.provider, model_id: entry.modelId };\n delete next.context_window;\n delete next.client_type;\n delete next.vision;\n delete next.pricing;\n delete next.display_name;\n // Leftover key from the old concatenated format (request_model_id): defensively stripped, never written to disk again.\n delete next.request_model_id;\n\n // Display name: **only written to disk when it differs from the built-in\n // catalog (looked up by the paired reference)** — preset models keep the\n // config clean, only user-edited ones (including those not found in the\n // catalog) get written into the TOML.\n const catNew = catalogEntryFor(entry.provider, entry.modelId);\n if (entry.displayName && entry.displayName !== catNew?.displayName) {\n next.display_name = entry.displayName;\n }\n if (entry.contextWindow !== undefined) next.context_window = entry.contextWindow;\n if (entry.clientType) next.client_type = entry.clientType;\n // Treated as supported by default: only written to disk when explicitly annotated (both true/false are kept; false drives a frontend blocking hint).\n if (entry.vision !== undefined) next.vision = entry.vision;\n if (entry.pricing !== undefined) {\n next.pricing = {\n unit: \"usd_per_mtok\",\n cache_read: entry.pricing.cacheRead,\n cache_write: entry.pricing.cacheWrite,\n output: entry.pricing.output,\n };\n }\n\n // credential is inlined on the entry; added/removed on top of the old value per the request (migrates automatically with the base entry when the key changes).\n if (entry.clearApiKey) {\n delete next.api_key;\n delete next.created_at;\n }\n if (entry.apiKey !== undefined) {\n next.api_key = entry.apiKey;\n next.created_at = new Date().toISOString();\n }\n if (entry.baseUrl === null) delete next.base_url;\n else if (entry.baseUrl !== undefined) next.base_url = entry.baseUrl;\n nextModels.push(next);\n }\n\n // default_model: when provided it must be present in models; when omitted the previous value is kept (the pointer follows a key rename; if it was deleted, it's removed).\n let defaultModel: ModelRefDto | undefined;\n if (req.defaultModel !== undefined) {\n if (!seen.has(refKey(req.defaultModel.provider, req.defaultModel.modelId))) {\n throw badRequest(\n `defaultModel 必须包含在 models 内:${showRef(req.defaultModel.provider, req.defaultModel.modelId)}。`,\n );\n }\n defaultModel = req.defaultModel;\n } else {\n const prevRef = optRef(raw.default_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) defaultModel = followed;\n }\n }\n\n // vision_model: same semantics as default_model; additionally must not be annotated vision=false (can't proxy-read images if unsupported).\n const targetOf = (ref: ModelRefDto) =>\n req.models.find((m) => m.provider === ref.provider && m.modelId === ref.modelId);\n let visionModel: ModelRefDto | undefined;\n if (req.visionModel !== undefined) {\n if (!seen.has(refKey(req.visionModel.provider, req.visionModel.modelId))) {\n throw badRequest(\n `visionModel 必须包含在 models 内:${showRef(req.visionModel.provider, req.visionModel.modelId)}。`,\n );\n }\n if (targetOf(req.visionModel)?.vision === false) {\n throw badRequest(\n `visionModel 不能指向标注为不支持图片的模型:${showRef(req.visionModel.provider, req.visionModel.modelId)}。`,\n );\n }\n visionModel = req.visionModel;\n } else {\n const prevRef = optRef(raw.vision_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) {\n // The former vision model is now annotated as not supporting images: the annotation takes priority, and the pointer is dropped as invalid.\n if (targetOf(followed)?.vision !== false) visionModel = followed;\n }\n }\n }\n\n const toRaw = (ref: ModelRefDto): RawTable => ({\n provider: ref.provider,\n model_id: ref.modelId,\n });\n const next: RawTable = { ...raw, models: nextModels };\n if (defaultModel !== undefined) next.default_model = toRaw(defaultModel);\n else delete next.default_model;\n if (visionModel !== undefined) next.vision_model = toRaw(visionModel);\n else delete next.vision_model;\n await this.writeRaw(projectId, next);\n return this.getModels(projectId);\n }\n}\n","/**\n * Agent service.\n *\n * The list is the union of \"DB index ∪ directory scan\": a subdirectory under\n * `<project>/` containing `agent_state/system_config.yaml` is treated as an Agent;\n * unmanaged ones found are backfilled into the DB — this handles Agents created\n * directly via the CLI.\n * Create: generate agent-<8hex>, initialize Agent State via core's `createAgent`,\n * then write name/description into system_config.yaml (parseDocument preserves the\n * template's comments).\n */\nimport fs from \"node:fs/promises\";\nimport { HttpError } from \"../http/errors.js\";\nimport {\n agentDir,\n agentsDir,\n agentsMdPath,\n BUILTIN_AGENT_IDS,\n createAgent as coreCreateAgent,\n isValidId,\n loadAgentVault,\n scheduleDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport { SEMANTIC_ID_PATTERN, SEMANTIC_ID_RULE } from \"./ids.js\";\nimport type { AgentConfigService } from \"./agent-config-service.js\";\n\nexport interface AgentListItem {\n agentId: string;\n name?: string;\n description?: string;\n createdAt?: string;\n /** Last config modification time: the later of system_config.yaml / AGENTS.md mtime. */\n updatedAt?: string;\n /** Tool count: number of tools.builtin + tools.mcpServers entries (MCP counted per server). */\n toolCount: number;\n /** Agent State version number (missing field treated as 1). */\n version: number;\n /** Number of vault keys. */\n vaultKeyCount: number;\n /** Number of scheduled tasks (count of .toml files under schedule/, including invalid ones). */\n scheduleCount: number;\n}\n\nexport class AgentService {\n constructor(\n private readonly root: string,\n private readonly agents: AgentsRepo,\n private readonly agentConfig: AgentConfigService,\n ) {}\n\n /** Union of DB index ∪ directory scan; unmanaged directory Agents are backfilled into the DB. */\n async listAgents(projectId: string): Promise<AgentListItem[]> {\n const known = new Map(this.agents.list(projectId).map((r) => [r.agentId, r]));\n\n let entries: string[] = [];\n try {\n const dirents = await fs.readdir(agentsDir(this.root, projectId), { withFileTypes: true });\n entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n // The Project's agents/ directory doesn't exist yet (no Agent directories): return from the DB index only.\n }\n for (const agentId of entries) {\n if (known.has(agentId) || !isValidId(agentId)) continue;\n const configPath = systemConfigPath(this.root, projectId, agentId);\n let createdAt: string;\n try {\n const stat = await fs.stat(configPath);\n createdAt = (stat.birthtime.getTime() > 0 ? stat.birthtime : stat.mtime).toISOString();\n } catch {\n continue; // A directory without system_config.yaml is not an Agent (e.g. a temp folder)\n }\n const row = { projectId, agentId, createdAt };\n this.agents.insertOrIgnore(row);\n known.set(agentId, row);\n }\n\n // Meta reads and mtime stats for each Agent run in parallel (Promise.all preserves the sorted order).\n const sorted = [...known.values()].sort((a, b) =>\n a.createdAt === b.createdAt\n ? a.agentId.localeCompare(b.agentId)\n : a.createdAt < b.createdAt\n ? -1\n : 1,\n );\n return Promise.all(\n sorted.map(async (row) => {\n const [meta, updatedAt, vaultKeyCount, scheduleCount] = await Promise.all([\n this.agentConfig.readCardMeta(projectId, row.agentId),\n this.configUpdatedAt(projectId, row.agentId),\n this.vaultKeyCount(projectId, row.agentId),\n this.scheduleCount(projectId, row.agentId),\n ]);\n return {\n agentId: row.agentId,\n ...meta,\n createdAt: row.createdAt,\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n vaultKeyCount,\n scheduleCount,\n };\n }),\n );\n }\n\n /** Number of vault keys (falls back to 0 on read failure). */\n private async vaultKeyCount(projectId: string, agentId: string): Promise<number> {\n try {\n return Object.keys(await loadAgentVault(this.root, projectId, agentId)).length;\n } catch {\n return 0;\n }\n }\n\n /** Number of scheduled tasks: count of .toml files under schedule/ (0 if the directory doesn't exist). */\n private async scheduleCount(projectId: string, agentId: string): Promise<number> {\n try {\n const names = await fs.readdir(scheduleDir(this.root, projectId, agentId));\n return names.filter((n) => n.endsWith(\".toml\")).length;\n } catch {\n return 0;\n }\n }\n\n /** Last config modification time: the later of system_config.yaml and AGENTS.md mtime; omitted if neither is readable. */\n private async configUpdatedAt(projectId: string, agentId: string): Promise<string | undefined> {\n const paths = [\n systemConfigPath(this.root, projectId, agentId),\n agentsMdPath(this.root, projectId, agentId),\n ];\n const times = await Promise.all(\n paths.map(async (p) => {\n try {\n return (await fs.stat(p)).mtime.getTime();\n } catch {\n return 0;\n }\n }),\n );\n const max = Math.max(...times);\n return max > 0 ? new Date(max).toISOString() : undefined;\n }\n\n /**\n * Delete an Agent: the sole built-in Agent\n * default_agent (shared with the CLI, the default conversation Agent) cannot be\n * deleted; callers must first drain any active run via manager.abortAgent.\n * The directory is deleted recursively (including Trace), and the DB's\n * agents/sessions index rows are removed along with it; usage records are kept\n * (historical stats are unaffected).\n */\n async deleteAgent(projectId: string, agentId: string): Promise<void> {\n if (BUILTIN_AGENT_IDS.includes(agentId)) {\n throw new HttpError(\n 409,\n \"cannot_delete_builtin_agent\",\n \"内置 Agent(default_agent)随 Project 供给,不能从 Web 删除。\",\n );\n }\n await fs.rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true });\n this.agents.delete(projectId, agentId);\n }\n\n /**\n * Create an Agent: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory within the Project — a 409\n * if taken, which naturally also blocks built-in Agent ids) → initialize State →\n * write name/description (name defaults to the id).\n */\n async createAgent(\n projectId: string,\n agentId: string,\n name?: string,\n description?: string,\n ): Promise<AgentListItem> {\n if (!SEMANTIC_ID_PATTERN.test(agentId)) {\n throw new HttpError(400, \"invalid_agent_id\", `Agent id 须为 2~64 位:${SEMANTIC_ID_RULE}。`);\n }\n const taken =\n this.agents.exists(projectId, agentId) ||\n (await fs.stat(agentDir(this.root, projectId, agentId)).then(\n () => true,\n () => false,\n ));\n if (taken) {\n throw new HttpError(409, \"agent_exists\", `Agent id 已被占用:${agentId}。`);\n }\n const displayName = name ?? agentId;\n await coreCreateAgent({ root: this.root, projectId, agentId });\n try {\n await this.agentConfig.updateConfig(projectId, agentId, {\n config: { name: displayName, ...(description !== undefined ? { description } : {}) },\n });\n } catch (err) {\n // If initialization fails partway through, clean up the directory: an orphaned\n // directory would make retries with this agent id 409 forever.\n await fs\n .rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n const createdAt = new Date().toISOString();\n this.agents.insertOrIgnore({ projectId, agentId, createdAt });\n // The init template ships with a default toolset and version number; read back the actual values.\n const meta = await this.agentConfig.readCardMeta(projectId, agentId);\n return {\n agentId,\n name: displayName,\n ...(description !== undefined ? { description } : {}),\n createdAt,\n updatedAt: createdAt,\n toolCount: meta.toolCount,\n version: meta.version,\n vaultKeyCount: 0,\n scheduleCount: 0,\n };\n }\n}\n","/**\n * Benchmark score reading (read-only display): walks `benchmarks/<id>/`, reads\n * `benchmark_config.toml` (title,\n * description, evaluation Model, per-case run count `runs`) and `scoreboard.yaml`\n * (evaluations[], scoreboard v2: each case carries a runs array and a summary).\n * Content is created and refined by benchmark_builder; the server only reads it.\n * Missing or corrupt files always degrade gracefully (title falls back to the\n * directory name, scores come back empty) rather than throwing.\n *\n * The three per-case metrics trust the file's own values; when missing they're\n * computed as the average over the runs array. The old format (no runs at the\n * case level, a single session_id) is parsed as a single run — the server backfills\n * one run entry.\n * Docs: /docs/self-improvement § \"Benchmark storage\".\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport { parse as parseYaml } from \"yaml\";\nimport { benchmarksDir } from \"@prismshadow/penguin-core\";\nimport type {\n BenchmarkCaseScore,\n BenchmarkEvaluation,\n BenchmarkRunScore,\n BenchmarkSummary,\n BenchmarksResponse,\n} from \"../api/types.js\";\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nfunction numberOr(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction stringOr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Shapes a single run entry: score is the minimum requirement, other fields tolerate being absent; a bad entry returns null and is dropped. */\nfunction toRun(v: unknown): BenchmarkRunScore | null {\n const r = asRecord(v);\n const score = numberOr(r.score);\n if (score === undefined) return null;\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n const sessionId = stringOr(r.session_id);\n return {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n };\n}\n\n/** Average of a metric across runs; undefined when there's no value at all (never forced to 0). */\nfunction averageOf(runs: BenchmarkRunScore[], pick: (r: BenchmarkRunScore) => number | undefined) {\n const values = runs.map(pick).filter((v): v is number => v !== undefined);\n if (values.length === 0) return undefined;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\n/**\n * Shapes a case-level entry (scoreboard v2): the three metrics trust the file's own\n * values, falling back to an average over runs when missing; the old format (no\n * runs, a single case-level session_id) is backfilled into a single run. case and a\n * score (from the file or derivable from runs) are the minimum requirement,\n * otherwise the entry is dropped.\n */\nfunction toCase(v: unknown): BenchmarkCaseScore | null {\n const cr = asRecord(v);\n const caseId = stringOr(cr.case);\n if (caseId === undefined) return null;\n const parsedRuns = Array.isArray(cr.runs)\n ? cr.runs.map(toRun).filter((r): r is BenchmarkRunScore => r !== null)\n : [];\n const score = numberOr(cr.score) ?? averageOf(parsedRuns, (r) => r.score);\n if (score === undefined) return null;\n const cost = numberOr(cr.cost) ?? averageOf(parsedRuns, (r) => r.cost);\n const durationMs = numberOr(cr.duration_ms) ?? averageOf(parsedRuns, (r) => r.durationMs);\n const sessionId = stringOr(cr.session_id);\n const runs: BenchmarkRunScore[] =\n parsedRuns.length > 0\n ? parsedRuns\n : [\n // The old format is parsed as a single run: the case-level values are that run's raw result.\n {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n },\n ];\n return {\n case: caseId,\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n runs,\n };\n}\n\n/** Shapes a single evaluation record: time and score are the minimum requirement, other fields (summary, etc.) tolerate being absent. */\nfunction toEvaluation(v: unknown): BenchmarkEvaluation | null {\n const r = asRecord(v);\n const time = r.time instanceof Date ? r.time.toISOString() : r.time;\n const score = numberOr(r.score);\n if (typeof time !== \"string\" || time === \"\" || score === undefined) return null;\n const cases: BenchmarkCaseScore[] = Array.isArray(r.cases)\n ? r.cases.map(toCase).filter((c): c is BenchmarkCaseScore => c !== null)\n : [];\n const summary = stringOr(r.summary);\n // Title and body are separate: summary_title is a one-line\n // conclusion, summary is the body text.\n const summaryTitle = stringOr(r.summary_title);\n // The Model actually used for this evaluation run (paired with provider):\n // charted curves are split into series by model, each with a distinct color.\n const modelId = stringOr(r.model_id);\n const provider = stringOr(r.provider);\n const version = numberOr(r.version);\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n return {\n time,\n ...(summaryTitle !== undefined ? { summaryTitle } : {}),\n ...(summary !== undefined ? { summary } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n score,\n ...(version !== undefined ? { version } : {}),\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n cases,\n };\n}\n\nexport class BenchmarkService {\n constructor(private readonly root: string) {}\n\n async list(projectId: string, agentId: string): Promise<BenchmarksResponse> {\n const dir = benchmarksDir(this.root, projectId, agentId);\n let items: Array<{ name: string; isDir: boolean }>;\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n items = entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));\n } catch {\n return { benchmarks: [] }; // Doesn't exist when unconfigured.\n }\n const benchmarks: BenchmarkSummary[] = [];\n for (const item of items.filter((i) => i.isDir).sort((a, b) => a.name.localeCompare(b.name))) {\n benchmarks.push(await this.readBenchmark(path.join(dir, item.name), item.name));\n }\n return { benchmarks };\n }\n\n private async readBenchmark(benchDir: string, id: string): Promise<BenchmarkSummary> {\n // benchmark_config.toml: title, description, and per-case run count (falls back\n // to defaults if corrupt). The model isn't part of the config — each evaluation\n // carries the Model actually used for that run.\n let title = id;\n let description: string | undefined;\n let runs: number | undefined;\n try {\n const config = asRecord(\n parseToml(await fs.readFile(path.join(benchDir, \"benchmark_config.toml\"), \"utf8\")),\n );\n if (typeof config.title === \"string\" && config.title !== \"\") title = config.title;\n if (typeof config.description === \"string\" && config.description !== \"\") {\n description = config.description;\n }\n const configRuns = numberOr(config.runs);\n if (configRuns !== undefined && Number.isInteger(configRuns) && configRuns >= 1) {\n runs = configRuns;\n }\n } catch {\n // Missing or corrupt: title falls back to the directory name.\n }\n\n // scoreboard.yaml: evaluations[] is appended over time; bad entries are dropped one by one.\n let evaluations: BenchmarkEvaluation[] = [];\n try {\n const scoreboard = asRecord(\n parseYaml(await fs.readFile(path.join(benchDir, \"scoreboard.yaml\"), \"utf8\")),\n );\n if (Array.isArray(scoreboard.evaluations)) {\n evaluations = scoreboard.evaluations\n .map(toEvaluation)\n .filter((e): e is BenchmarkEvaluation => e !== null);\n }\n } catch {\n // No scores yet.\n }\n\n // Case count: number of case subfolders (the statement/rubric structure isn't validated here).\n let caseCount = 0;\n try {\n const entries = await fs.readdir(benchDir, { withFileTypes: true });\n caseCount = entries.filter((e) => e.isDirectory()).length;\n } catch {\n // Stays at 0.\n }\n\n return {\n id,\n title,\n ...(description !== undefined ? { description } : {}),\n ...(runs !== undefined ? { runs } : {}),\n caseCount,\n evaluations,\n };\n }\n}\n","/**\n * Agent State version snapshots and export/import.\n *\n * A snapshot = `snapshots/v<version>.tar.gz`, packaging `agent_state/` (archive\n * entries are rooted at `agent_state/`), **excluding `.vault.toml`** (secrets never\n * go into a snapshot); if a snapshot for the same version already exists, it isn't\n * repacked. Import goes by the `version` inside the package and keeps the current\n * vault; a snapshot of the current version is automatically taken before import;\n * importing a package version equal to or lower than the current version requires\n * explicit confirmation (otherwise 409).\n * Docs: /docs/self-improvement § \"Snapshots and versions\".\n */\nimport { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as tar from \"tar\";\nimport { parse as parseYaml } from \"yaml\";\nimport {\n agentDir,\n agentStateDir,\n agentStateVersion,\n agentVaultPath,\n snapshotsDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Vault file name inside a snapshot/import package (used for archive filtering). */\nconst VAULT_BASENAME = \".vault.toml\";\n\nfunction isVaultEntry(entryPath: string): boolean {\n return path.posix.basename(entryPath.replaceAll(\"\\\\\", \"/\")) === VAULT_BASENAME;\n}\n\nexport class SnapshotService {\n constructor(private readonly root: string) {}\n\n /** Current Agent State version number (missing field treated as 1); throws 404 if the Agent doesn't exist. */\n async currentVersion(projectId: string, agentId: string): Promise<number> {\n let raw: string;\n try {\n raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n throw new HttpError(404, \"agent_not_found\", \"Agent 不存在。\");\n }\n const parsed = parseYaml(raw) as { version?: unknown } | null;\n return agentStateVersion({\n version: typeof parsed?.version === \"number\" ? parsed.version : undefined,\n });\n }\n\n /** Ensures a snapshot exists for the current version (not repacked for the same version), returns the snapshot file path and version number. */\n async ensureSnapshot(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string }> {\n const version = await this.currentVersion(projectId, agentId);\n const dir = snapshotsDir(this.root, projectId, agentId);\n const file = path.join(dir, `v${version}.tar.gz`);\n try {\n await fs.access(file);\n return { version, file };\n } catch {\n // No snapshot for this version yet: pack it.\n }\n await fs.mkdir(dir, { recursive: true });\n const tmp = `${file}.tmp-${randomBytes(4).toString(\"hex\")}`;\n await tar.create(\n {\n gzip: true,\n cwd: agentDir(this.root, projectId, agentId),\n file: tmp,\n portable: true,\n filter: (p) => !isVaultEntry(p),\n },\n [\"agent_state\"],\n );\n await fs.rename(tmp, file);\n return { version, file };\n }\n\n /** Export: automatically packs a snapshot first if none exists, returns the info needed for download. */\n async exportArchive(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string; fileName: string }> {\n const { version, file } = await this.ensureSnapshot(projectId, agentId);\n return { version, file, fileName: `${agentId}-v${version}.tar.gz` };\n }\n\n /**\n * Import: validates the package structure and `version`, compares versions\n * (higher than current imports directly, same version or older requires\n * `confirm`), automatically snapshots the current version before import, then\n * replaces `agent_state/` while keeping the current vault.\n */\n async importArchive(\n projectId: string,\n agentId: string,\n archive: Buffer,\n confirm: boolean,\n ): Promise<{ version: number }> {\n const current = await this.currentVersion(projectId, agentId);\n const base = agentDir(this.root, projectId, agentId);\n const staging = path.join(base, `.import-${randomBytes(6).toString(\"hex\")}`);\n await fs.mkdir(staging, { recursive: true });\n try {\n const archiveFile = path.join(staging, \"archive.tar.gz\");\n await fs.writeFile(archiveFile, archive);\n const extractDir = path.join(staging, \"extracted\");\n await fs.mkdir(extractDir, { recursive: true });\n try {\n await tar.extract({\n file: archiveFile,\n cwd: extractDir,\n filter: (p) => !isVaultEntry(p),\n });\n } catch {\n throw badRequest(\"导入失败:不是合法的 tar.gz 快照包。\");\n }\n\n // Validation: the package must contain agent_state/system_config.yaml, and version must be valid.\n const configPath = path.join(extractDir, \"agent_state\", \"system_config.yaml\");\n let parsed: unknown;\n try {\n parsed = parseYaml(await fs.readFile(configPath, \"utf8\"));\n } catch {\n throw badRequest(\"导入失败:包内缺少 agent_state/system_config.yaml。\");\n }\n if (\n parsed === null ||\n typeof parsed !== \"object\" ||\n typeof (parsed as { system_prompt?: unknown }).system_prompt !== \"string\"\n ) {\n throw badRequest(\"导入失败:包内 system_config.yaml 非法。\");\n }\n const incomingRaw = (parsed as { version?: unknown }).version;\n if (\n incomingRaw !== undefined &&\n (!Number.isInteger(incomingRaw) || (incomingRaw as number) < 1)\n ) {\n throw badRequest(\"导入失败:包内 version 非法。\");\n }\n const incoming = agentStateVersion({\n version: typeof incomingRaw === \"number\" ? incomingRaw : undefined,\n });\n if (incoming <= current && !confirm) {\n throw new HttpError(\n 409,\n \"version_conflict\",\n `包版本 v${incoming} 不高于当前 v${current},需要确认后导入。`,\n );\n }\n\n // Automatically snapshots the current version before import (reused if one already exists for that version), so a mistaken import can be rolled back.\n await this.ensureSnapshot(projectId, agentId);\n\n // Replace agent_state: first merge the current vault into the staging\n // directory to be swapped in (extraction already filtered out any vault\n // inside the package, so no conflict), making the replacement a pure rename\n // swap — once the swap lands, there's no further write that can fail. The\n // vault never goes into a snapshot, so if recovery fails after the swap, the\n // `finally` cleanup of staging would delete the only vault copy with no way\n // to roll back.\n const stateDir = agentStateDir(this.root, projectId, agentId);\n const incomingState = path.join(extractDir, \"agent_state\");\n let vault: Buffer | null = null;\n try {\n vault = await fs.readFile(agentVaultPath(this.root, projectId, agentId));\n } catch {\n // No vault means nothing to preserve.\n }\n if (vault !== null) {\n await fs.writeFile(path.join(incomingState, VAULT_BASENAME), vault, { mode: 0o600 });\n }\n const trash = path.join(staging, \"replaced-agent_state\");\n await fs.rename(stateDir, trash);\n try {\n await fs.rename(incomingState, stateDir);\n } catch (err) {\n await fs.rename(trash, stateDir); // Rollback: restore the old directory if swapping in the new one fails.\n throw err;\n }\n return { version: incoming };\n } finally {\n await fs.rm(staging, { recursive: true, force: true });\n }\n }\n}\n","/**\n * Project service.\n *\n * The single implementation point for authorization rules: `requireProjectAccess`\n * (owner or member, otherwise 404 without leaking existence) and\n * `requireProjectOwner` (owner only; 403 when known to be accessible, 404 when not)\n * are reused by every route; the non-throwing `canAccess` (for error attribution)\n * is likewise just a sibling wrapper around them — all three share the single\n * `resolveAccess` decision, with no second rule set maintained separately.\n * Also handles Project create / list / delete, member authorization, and initial\n * Project provisioning at signup.\n */\nimport fs from \"node:fs/promises\";\nimport { DEFAULT_PROJECT_ID, projectDir, provisionProjectAgents } from \"@prismshadow/penguin-core\";\nimport type { MemberInfo, ProjectRole, ProjectSummary } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\nimport type { MembersRepo } from \"../db/repos/members.js\";\nimport type { ProjectRow, ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SchedulesRepo } from \"../db/repos/schedules.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport {\n PROJECT_ID_MAX_LENGTH,\n PROJECT_SUFFIX_PATTERN,\n SEMANTIC_ID_PATTERN,\n SEMANTIC_ID_RULE,\n} from \"./ids.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\n/** Fallback timeout for waiting on runs to settle before deleting a Project. */\nconst ABORT_SETTLE_TIMEOUT_MS = 5000;\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport interface ProjectServiceDeps {\n root: string;\n users: UsersRepo;\n projects: ProjectsRepo;\n members: MembersRepo;\n agents: AgentsRepo;\n sessions: SessionsRepo;\n usage: UsageRepo;\n errors: ErrorsRepo;\n schedules: SchedulesRepo;\n projectConfig: ProjectConfigService;\n manager: SessionManager;\n}\n\nexport class ProjectService {\n constructor(private readonly deps: ProjectServiceDeps) {}\n\n // —— Authorization rules (single implementation point) ——\n\n /**\n * The **sole** implementation of the owner / member check: returns the row with\n * a role if accessible, otherwise null. `requireProjectAccess` below (throws 404)\n * and `canAccess` (returns boolean) are both just wrappers around it — there's\n * only one copy of the decision rule, since writing it twice would eventually\n * drift out of sync.\n */\n private resolveAccess(\n userId: string,\n projectId: string,\n ): (ProjectRow & { role: ProjectRole }) | null {\n const row = this.deps.projects.findById(projectId);\n if (!row) return null;\n if (row.ownerUserId === userId) return { ...row, role: \"owner\" };\n if (this.deps.members.isMember(projectId, userId)) return { ...row, role: \"member\" };\n return null;\n }\n\n /** Accessible by owner or member; otherwise 404 (does not leak Project existence). */\n requireProjectAccess(userId: string, projectId: string): ProjectRow & { role: ProjectRole } {\n const row = this.resolveAccess(userId, projectId);\n if (!row) {\n throw new HttpError(404, \"project_not_found\", \"Project 不存在或无权访问。\");\n }\n return row;\n }\n\n /**\n * The non-throwing version of the same check: used for **error attribution**\n * (app.onError) — that runs on the error-handling path, where throwing another\n * 404 would only break error handling; whether access is granted shouldn't be\n * expressed as an exception there anyway.\n */\n canAccess(userId: string, projectId: string): boolean {\n return this.resolveAccess(userId, projectId) !== null;\n }\n\n /** Owner only: 403 when known accessible as a member; 404 when not accessible. */\n requireProjectOwner(userId: string, projectId: string): ProjectRow {\n const row = this.requireProjectAccess(userId, projectId);\n if (row.role !== \"owner\") {\n throw new HttpError(403, \"owner_required\", \"该操作仅 Project owner 可执行。\");\n }\n return row;\n }\n\n /** List of project_ids accessible to the current user (owned + granted access) (used by workspace-guard). */\n accessibleProjectIds(userId: string): string[] {\n return this.deps.projects.listAccessible(userId).map((p) => p.projectId);\n }\n\n // —— Project lifecycle ——\n\n /** List of owned + granted-access Projects; display names are read from each project_config.toml. */\n async listProjects(userId: string): Promise<ProjectSummary[]> {\n const rows = this.deps.projects.listAccessible(userId);\n return Promise.all(\n rows.map(async (row) => {\n const name = await this.deps.projectConfig.getName(row.projectId);\n return {\n projectId: row.projectId,\n ...(name !== undefined ? { name } : {}),\n role: row.role,\n ownerUserId: row.ownerUserId,\n createdAt: row.createdAt,\n };\n }),\n );\n }\n\n /**\n * Create a Project: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory — 409 if taken), the initial\n * config is written (display name defaults to the id), and the built-in Agent is\n * initialized.\n * A non-admin's id is forced to be \"<username>-<suffix>\", where the suffix is\n * lowercase letters, digits, and underscores only — the hyphen is a reserved\n * separator, usernames never contain a hyphen, so the first hyphen is the\n * ownership boundary and the prefix can never be crafted from another username;\n * an admin's id contains no hyphen (occupying no user's namespace).\n */\n async createProject(owner: UserRow, projectId: string, name?: string): Promise<ProjectSummary> {\n if (owner.isAdmin) {\n if (!SEMANTIC_ID_PATTERN.test(projectId)) {\n throw new HttpError(\n 400,\n \"invalid_project_id\",\n `Project id 须为 2~64 位:${SEMANTIC_ID_RULE}(连字符保留作用户命名空间分隔)。`,\n );\n }\n } else {\n const prefix = `${owner.userId}-`;\n const suffix = projectId.startsWith(prefix) ? projectId.slice(prefix.length) : \"\";\n if (!PROJECT_SUFFIX_PATTERN.test(suffix) || projectId.length > PROJECT_ID_MAX_LENGTH) {\n throw new HttpError(\n 400,\n \"project_id_prefix_required\",\n `Project id 须以 ${prefix} 开头,后接小写字母、数字或下划线(总长不超过 ${PROJECT_ID_MAX_LENGTH})。`,\n );\n }\n }\n if (\n this.deps.projects.findById(projectId) !== null ||\n (await dirExists(projectDir(this.deps.root, projectId)))\n ) {\n throw new HttpError(409, \"project_exists\", `Project id 已被占用:${projectId}。`);\n }\n const displayName = name ?? projectId;\n const createdAt = new Date().toISOString();\n // Insert the DB row first: the primary key is the final arbiter for concurrent\n // creation with the same id (the duplicate check above has an await gap), and a\n // conflict is mapped to 409 with **no cleanup** — the directory belongs to the\n // winner, and cleaning up here would wrongly delete the other side's data.\n try {\n this.deps.projects.insert({ projectId, ownerUserId: owner.userId, createdAt });\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"UNIQUE\")) {\n throw new HttpError(409, \"project_exists\", `Project id 已被占用:${projectId}。`);\n }\n throw err;\n }\n // If file initialization fails, roll back the DB row and clean up the\n // directory: an orphaned directory would make retries with this id 409 forever\n // (a typical scenario: signup failure rolled back the user row, but the\n // <username>-default_project directory was left behind).\n try {\n await fs.mkdir(projectDir(this.deps.root, projectId), { recursive: true });\n await this.deps.projectConfig.writeInitialConfig(projectId, displayName);\n await this.provisionBuiltinAgents(projectId);\n } catch (err) {\n this.deps.projects.delete(projectId);\n await fs\n .rm(projectDir(this.deps.root, projectId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n return {\n projectId,\n name: displayName,\n role: \"owner\",\n ownerUserId: owner.userId,\n createdAt,\n };\n }\n\n /**\n * Initial Project provisioned at signup:\n * the built-in admin adopts `default_project` (if the directory already exists,\n * it's adopted directly without overwriting existing config — shared with the\n * CLI); other users get `<username>-default_project` created, with display name\n * defaulting to the username.\n */\n async provisionInitialProject(user: UserRow, isAdmin: boolean): Promise<void> {\n if (!isAdmin) {\n await this.createProject(user, `${user.userId}-${DEFAULT_PROJECT_ID}`, user.userId);\n return;\n }\n const projectId = DEFAULT_PROJECT_ID;\n // Initialize the built-in Agent (loaded without overwriting if it already exists); this also ensures the directory exists.\n await this.provisionBuiltinAgents(projectId);\n // Adopting an existing directory doesn't go through writeInitialConfig: preset\n // models and the default model are backfilled instead (only when there are no\n // models at all; a default_project already configured via the CLI is left\n // as-is).\n await this.deps.projectConfig.ensurePresetModels(projectId);\n this.deps.projects.insert({\n projectId,\n ownerUserId: user.userId,\n createdAt: new Date().toISOString(),\n });\n }\n\n /**\n * Delete a Project (owner): default_project is refused; deleting the user's\n * **last accessible Project** is refused too (deleting it would leave the list\n * empty, with no Project to select in the Web client and the page stuck on a\n * skeleton screen — a typical case being a non-first user deleting the initial\n * Project provisioned at signup); active runs are drained first, then the DB and\n * directory are cleared.\n */\n async deleteProject(userId: string, projectId: string): Promise<void> {\n this.requireProjectOwner(userId, projectId);\n if (projectId === DEFAULT_PROJECT_ID) {\n throw new HttpError(\n 409,\n \"cannot_delete_default_project\",\n \"default_project 与 CLI 共用,不能从 Web 删除。\",\n );\n }\n if (this.deps.projects.listAccessible(userId).length <= 1) {\n throw new HttpError(\n 409,\n \"cannot_delete_last_project\",\n \"这是当前账号最后一个 Project,删除后将无 Project 可用;请先创建新的 Project。\",\n );\n }\n await this.destroyProject(projectId);\n }\n\n /**\n * The actual deletion (no authorization or protection checks): shared by\n * deleteProject and the cascade cleanup when an admin deletes a user.\n * Abort follow-up (writing the abort event to Trace, etc.) happens\n * asynchronously: waits for runs to settle (capped at 5s) before deleting the\n * directory, to avoid the Trace writer recreating the directory after deletion.\n */\n async destroyProject(projectId: string): Promise<void> {\n const runnings = this.deps.manager.abortProject(projectId);\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, ABORT_SETTLE_TIMEOUT_MS).unref?.()),\n ]);\n }\n this.deps.projects.delete(projectId); // project_members cascade-deleted\n this.deps.agents.deleteByProject(projectId);\n this.deps.sessions.deleteByProject(projectId);\n this.deps.usage.deleteByProject(projectId);\n this.deps.errors.deleteByProject(projectId);\n this.deps.schedules.deleteByProject(projectId);\n await fs.rm(projectDir(this.deps.root, projectId), { recursive: true, force: true });\n }\n\n // —— Member authorization ——\n\n /** Member list: owner (role=owner) + members. */\n listMembers(userId: string, projectId: string): MemberInfo[] {\n const project = this.requireProjectAccess(userId, projectId);\n const members = this.deps.members.list(projectId);\n return [\n { userId: project.ownerUserId, role: \"owner\", createdAt: project.createdAt },\n ...members.map((m) => ({\n userId: m.userId,\n role: \"member\" as const,\n createdAt: m.createdAt,\n })),\n ];\n }\n\n /** Grant member access (owner): invites by username; 404 if the user doesn't exist, 409 for the owner themself or an existing member. */\n addMember(userId: string, projectId: string, targetUserId: string): MemberInfo {\n const project = this.requireProjectOwner(userId, projectId);\n const target = this.deps.users.findById(targetUserId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `用户不存在:${targetUserId}。`);\n }\n if (target.userId === project.ownerUserId) {\n throw new HttpError(409, \"already_owner\", \"owner 无需授权给自己。\");\n }\n if (this.deps.members.isMember(projectId, target.userId)) {\n throw new HttpError(409, \"already_member\", `${targetUserId} 已是该 Project 的成员。`);\n }\n const createdAt = new Date().toISOString();\n this.deps.members.insert({ projectId, userId: target.userId, createdAt });\n return { userId: target.userId, role: \"member\", createdAt };\n }\n\n /** Revoke member access (owner). */\n removeMember(userId: string, projectId: string, targetUserId: string): void {\n this.requireProjectOwner(userId, projectId);\n if (!this.deps.members.isMember(projectId, targetUserId)) {\n throw new HttpError(404, \"member_not_found\", `该 Project 没有成员:${targetUserId}。`);\n }\n this.deps.members.delete(projectId, targetUserId);\n }\n\n /**\n * Ensures the Project's built-in Agent exists (the sole built-in Agent\n * default_agent; initialized if the directory is empty, otherwise loaded without\n * overwriting) and indexes it. createdAt increments by 1ms in preset order, so\n * built-in Agents stably sort first; other Agents backfilled by directory\n * scanning are sorted by their own createdAt and are outside the scope of this\n * guarantee.\n */\n private async provisionBuiltinAgents(projectId: string): Promise<void> {\n const agentIds = await provisionProjectAgents({ root: this.deps.root, projectId });\n const base = Date.now();\n agentIds.forEach((agentId, i) => {\n this.deps.agents.insertOrIgnore({\n projectId,\n agentId,\n createdAt: new Date(base + i).toISOString(),\n });\n });\n }\n}\n","/**\n * Session index service.\n *\n * The list is DB index ∪ Trace directory discovery: scans\n * `<agent>/traces/<date>/<session_id>_<index3>.jsonl`; an unmanaged Session (e.g.\n * one started via the CLI) has its first line's session_meta read for\n * (provider, model_id) / workspace, which is backfilled into a DB row\n * (approval_mode defaults, createdAt is taken from the timestamp embedded in\n * session_id).\n * Create: via core's `agent.createSession` (model reference as a provider + modelId\n * pair; defaults to the Project's default reference, 400 if there is none; omitting\n * provider goes through resolveModelRef for unique resolution); the new Session is\n * added to session-manager's active table (state idle).\n */\nimport path from \"node:path\";\nimport { readdir } from \"node:fs/promises\";\nimport {\n createAgent,\n isSessionMeta,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { ApprovalMode, SessionInfo } from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\nconst SESSION_ID_TS_RE = /^session-(\\d{4})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-[0-9a-f]{8}$/;\n\n/** Derives creation time from the local timestamp embedded in session_id; returns null if it doesn't match. */\nexport function sessionIdCreatedAt(sessionId: string): string | null {\n const m = SESSION_ID_TS_RE.exec(sessionId);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m;\n const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nexport interface SessionServiceDeps {\n root: string;\n sessions: SessionsRepo;\n manager: SessionManager;\n projectConfig: ProjectConfigService;\n}\n\nexport class SessionService {\n constructor(private readonly deps: SessionServiceDeps) {}\n\n /** DB row -> SessionInfo (run status and pending approval count come from session-manager). */\n toInfo(row: SessionRow, hasTrace: boolean): SessionInfo {\n return {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n workspace: row.workspace,\n approvalMode: row.approvalMode,\n ...(row.title !== null ? { title: row.title } : {}),\n ...(row.source != null ? { source: row.source } : {}),\n createdAt: row.createdAt,\n status: this.deps.manager.statusOf(row.sessionId),\n pendingApprovalCount: this.deps.manager.pendingApprovalCount(row.sessionId),\n hasTrace,\n archived: (row.archivedAt ?? null) !== null,\n };\n }\n\n /** Whether this Session already has a Trace record (a Task has been run). */\n async hasTrace(row: SessionRow): Promise<boolean> {\n const ids = await this.discoverTraceSessionIds(row.projectId, row.agentId);\n return ids.has(row.sessionId);\n }\n\n /** List: DB ∪ Trace directory discovery, sorted by createdAt descending. */\n async listSessions(projectId: string, agentId: string): Promise<SessionInfo[]> {\n const traceIds = await this.discoverTraceSessionIds(projectId, agentId);\n const rows = new Map(\n this.deps.sessions.listByAgent(projectId, agentId).map((r) => [r.sessionId, r]),\n );\n\n // Unmanaged Trace Sessions: backfill an index row by reading the first line's session_meta.\n for (const sessionId of traceIds) {\n if (rows.has(sessionId)) continue;\n const discovered = await this.adoptTraceSession(projectId, agentId, sessionId);\n if (discovered) rows.set(sessionId, discovered);\n }\n\n return [...rows.values()]\n .sort(\n (a, b) => b.createdAt.localeCompare(a.createdAt) || b.sessionId.localeCompare(a.sessionId),\n )\n .map((row) => this.toInfo(row, traceIds.has(row.sessionId)));\n }\n\n /**\n * Session stats (Agents list card): total count = size of the union of DB index\n * ∪ Trace directory discovery; activity = number of active Sessions per day over\n * the last `days` days (deduplicated count of Sessions created that day or with a\n * Trace record that day; index 0 = earliest, last index = today). Counts only —\n * does not backfill index rows.\n */\n async sessionStats(\n projectId: string,\n agentId: string,\n days: number,\n ): Promise<{ sessionCount: number; activity: number[] }> {\n const all = new Set<string>();\n const byDate = new Map<string, Set<string>>();\n const mark = (date: string, sessionId: string): void => {\n all.add(sessionId);\n const set = byDate.get(date) ?? new Set<string>();\n set.add(sessionId);\n byDate.set(date, set);\n };\n\n // Trace directory: the date directory name is the local date (yyyy-mm-dd) that core uses when writing to disk.\n const dir = tracesDir(this.deps.root, projectId, agentId);\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match) mark(dateDir, match[1]!);\n }\n }\n // DB index: the creation day also counts as active (a Session that hasn't run a Task yet produces no Trace).\n for (const row of this.deps.sessions.listByAgent(projectId, agentId)) {\n const created = new Date(row.createdAt);\n if (Number.isNaN(created.getTime())) all.add(row.sessionId);\n else mark(localDate(created), row.sessionId);\n }\n\n const activity: number[] = [];\n const now = new Date();\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);\n activity.push(byDate.get(localDate(d))?.size ?? 0);\n }\n return { sessionCount: all.size, activity };\n }\n\n /**\n * Create a Session: model reference `(provider, modelId)` as a pair; defaults to\n * the Project's default reference (400 prompting to configure a model first if\n * there is none); when provider is omitted, core's resolveModelRef performs\n * unique resolution (400 on zero matches / ambiguity). `workspace` is already\n * validated by the route guard. The new Session is added to the active table\n * (idle).\n */\n async createSession(args: {\n projectId: string;\n agentId: string;\n /** Upstream id of the session's model (paired with provider); defaults to the Project's default reference. */\n modelId?: string;\n /** The provider group for `modelId`; if omitted, resolveModelRef performs unique resolution. */\n provider?: string;\n workspace?: string;\n approvalMode?: ApprovalMode;\n /** Session source marker (schedule when triggered by a scheduled task; defaults to user-created). */\n source?: \"schedule\";\n }): Promise<SessionInfo> {\n let modelId = args.modelId;\n let provider = args.provider;\n if (modelId === undefined) {\n const def = await this.deps.projectConfig.getDefaultModelRef(args.projectId);\n if (def === undefined) {\n throw new HttpError(\n 400,\n \"no_default_model\",\n \"该 Project 尚未配置默认模型,请先在「模型」页添加模型并设为默认。\",\n );\n }\n modelId = def.model_id;\n provider = def.provider;\n }\n const agent = await createAgent({\n root: this.deps.root,\n projectId: args.projectId,\n agentId: args.agentId,\n });\n let session;\n try {\n session = await agent.createSession({\n modelId,\n ...(provider !== undefined ? { provider } : {}),\n ...(args.workspace !== undefined ? { workspaceDir: args.workspace } : {}),\n });\n } catch (err) {\n // A missing credential is its own category (the frontend shows localized text\n // by code); other core errors (zero matches / ambiguous reference, Workspace\n // not existing, etc.) are collapsed to 400 — the guard already blocks most cases.\n if (isMissingCredential(err)) throw modelCredentialMissing(modelId);\n throw new HttpError(\n 400,\n \"session_create_failed\",\n err instanceof Error ? err.message : String(err),\n );\n }\n const row: SessionRow = {\n sessionId: session.sessionId,\n projectId: args.projectId,\n agentId: args.agentId,\n provider: session.provider,\n modelId: session.modelId,\n workspace: session.workspaceDir,\n approvalMode: args.approvalMode ?? \"allow-all\",\n title: null,\n createdAt: new Date().toISOString(),\n ...(args.source !== undefined ? { source: args.source } : {}),\n };\n this.deps.sessions.insert(row);\n this.deps.manager.adopt(row, session);\n return this.toInfo(row, false);\n }\n\n /** Scans the Trace directory to get the set of session_ids with records. */\n private async discoverTraceSessionIds(projectId: string, agentId: string): Promise<Set<string>> {\n const dir = tracesDir(this.deps.root, projectId, agentId);\n const ids = new Set<string>();\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match) ids.add(match[1]!);\n }\n }\n return ids;\n }\n\n /** Adopts a Session that exists only in the Trace directory: reads session_meta from the first line of the earliest index file. */\n private async adoptTraceSession(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<SessionRow | null> {\n const dir = tracesDir(this.deps.root, projectId, agentId);\n let earliest: { path: string; index: number } | null = null;\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match || match[1] !== sessionId) continue;\n const index = Number(match[2]);\n if (!earliest || index < earliest.index) {\n earliest = { path: path.join(dir, dateDir, file), index };\n }\n }\n }\n if (!earliest) return null;\n let messages;\n try {\n messages = await readTraceTolerant(earliest.path);\n } catch {\n return null; // Corrupt file: skip (does not block the list)\n }\n const meta = messages.find(isSessionMeta);\n if (!meta) return null;\n // An older Trace version's session_meta lacks provider (the model reference\n // wasn't split into separate fields yet): no backward compat, skip adoption\n // (core will give a clear error on resume; the product hasn't launched yet, so\n // old data can simply be deleted and recreated).\n if (typeof meta.payload.provider !== \"string\") return null;\n const row: SessionRow = {\n sessionId,\n projectId,\n agentId,\n provider: meta.payload.provider,\n modelId: meta.payload.model_id,\n workspace: meta.payload.workspace,\n // The approval mode for an unmanaged Session (started via the CLI) isn't in the Trace, so it's backfilled with the default value.\n approvalMode: \"allow-all\",\n title: null,\n createdAt: sessionIdCreatedAt(sessionId) ?? meta.timestamp,\n };\n // Idempotent backfill: concurrent list calls may discover the same Session for the first time simultaneously (consistent with AgentsRepo's convention).\n this.deps.sessions.insertOrIgnore(row);\n return row;\n }\n}\n\n/** Local date as yyyy-mm-dd (matches the Trace date directory convention: core's internal formatLocalDate, not publicly exported). */\nfunction localDate(d: Date): string {\n const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\nasync function listDirsSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFilesSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n","/**\n * Trace service.\n *\n * History messages: all of the Session's index files concatenated in order\n * (readTraceTolerant, tolerating a truncated last line), containing only the\n * complete messages and events that were actually written to Trace (naturally\n * excluding partial_*); in-flight increments are continued by SSE.\n * Performance analysis is derived from a single Trace file: nearest-neighbor\n * pairing of request_begin/end, tool call duration pairing, reconnect / compaction\n * counts, and Token trend.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { agentsDir, readTraceTolerant, tracesDir } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n AgentTracesResponse,\n RequestSpan,\n ToolCallSpan,\n TraceAnalysisResponse,\n TraceEventsResponse,\n TraceFileInfo,\n TraceModelSegment,\n TraceTaskStats,\n TraceToolSpan,\n UsageTrendPointInTrace,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\n\n/** Recursion depth cap for sub-session expansion (run_subagent depth is already constrained by the SDK; this is just a defensive backstop against cycles). */\nconst MAX_SUBAGENT_DEPTH = 4;\n\ninterface LocatedFile {\n path: string;\n date: string;\n index: number;\n}\n\n/**\n * A **direct sub-session pointer** (the `subagent` event in the parent Trace) ->\n * the sub-session's Session id. The pointer only\n * records the Session id; the sub-session's Agent is located within the Project by\n * its Trace file.\n */\nfunction subagentPointer(msg: OmniMessage): string | null {\n if (msg.type !== \"event_msg\") return null;\n const p = msg.payload as { type?: string; session_id?: unknown };\n if (p.type !== \"subagent\" || typeof p.session_id !== \"string\" || p.session_id === \"\") {\n return null;\n }\n return p.session_id;\n}\n\nasync function listDirs(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport class TraceService {\n constructor(private readonly root: string) {}\n\n /** All of this Session's Trace files (sorted by index ascending). */\n private async locateAll(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<LocatedFile[]> {\n const dir = tracesDir(this.root, projectId, agentId);\n const out: LocatedFile[] = [];\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match || match[1] !== sessionId) continue;\n out.push({ path: path.join(dir, dateDir, file), date: dateDir, index: Number(match[2]) });\n }\n }\n return out.sort((a, b) => a.index - b.index);\n }\n\n /** Deletes all of this Session's Trace files (called when the Session is deleted). */\n async deleteSessionTraces(projectId: string, agentId: string, sessionId: string): Promise<void> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n for (const file of files) {\n await fs.rm(file.path, { force: true });\n }\n }\n\n /**\n * History messages: all index files concatenated in order, with sub-sessions\n * **expanded in place**.\n *\n * The parent Trace only records a `subagent` pointer event at the spawn point\n * (recording just the child Session id; the content lives in the child\n * Session's own Trace). Here the pointer is used to locate the child Trace\n * within the Project, read it recursively, and splice the child messages —\n * tagged with an origin chain — back in at the pointer's position, so that when\n * the session is reopened, the frontend can re-attach the sub-session to the\n * run_subagent tool card via origin (the child Trace's first `session_meta`,\n * once given an origin, takes the same shape as what's forwarded over the live\n * stream). When expansion succeeds, the pointer event itself is no longer\n * emitted; when the child Trace is missing (deleted), the pointer event is kept\n * so API consumers can still know it existed.\n */\n async readMessages(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<OmniMessage[]> {\n return this.readMessagesExpanded(projectId, agentId, sessionId, {\n index: null,\n ancestry: new Set([sessionId]),\n depth: 0,\n });\n }\n\n /**\n * A Project-wide session location index (sessionId -> agentId): built by\n * scanning every Agent's traces directory. Built lazily the first time a\n * subagent pointer is encountered, then reused across the whole readMessages\n * call — rescanning per pointer would blow up into tens of thousands of readdir\n * calls under multiple sub-sessions plus recursive expansion.\n */\n private async buildSessionIndex(projectId: string): Promise<Map<string, string>> {\n const index = new Map<string, string>();\n for (const agentId of await listDirs(agentsDir(this.root, projectId))) {\n const dir = tracesDir(this.root, projectId, agentId);\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match && !index.has(match[1]!)) index.set(match[1]!, agentId);\n }\n }\n }\n return index;\n }\n\n private async readMessagesExpanded(\n projectId: string,\n agentId: string,\n sessionId: string,\n ctx: { index: Map<string, string> | null; ancestry: Set<string>; depth: number },\n ): Promise<OmniMessage[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: OmniMessage[] = [];\n for (const file of files) {\n for (const msg of await readTraceTolerant(file.path)) {\n // The depth cap guards against runaway recursion; ancestry guards against a\n // cyclic pointer (a tampered Trace pointing to itself/an ancestor is not expanded).\n const childSid = ctx.depth < MAX_SUBAGENT_DEPTH ? subagentPointer(msg) : null;\n if (!childSid || ctx.ancestry.has(childSid)) {\n out.push(msg);\n continue;\n }\n ctx.index ??= await this.buildSessionIndex(projectId);\n const childAgent = ctx.index.get(childSid);\n let nested: OmniMessage[] = [];\n if (childAgent) {\n ctx.ancestry.add(childSid);\n nested = await this.readMessagesExpanded(projectId, childAgent, childSid, {\n ...ctx,\n depth: ctx.depth + 1,\n });\n ctx.ancestry.delete(childSid);\n }\n // Child Trace missing (deleted): keep the pointer event, since the sub-session's content can't be recovered.\n if (nested.length === 0) {\n out.push(msg);\n continue;\n }\n for (const m of nested) out.push({ ...m, origin: [childSid, ...(m.origin ?? [])] });\n }\n }\n return out;\n }\n\n /** List of Trace files (index / date / size / mtime). */\n async listTraceFiles(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<TraceFileInfo[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: TraceFileInfo[] = [];\n for (const file of files) {\n const stat = await fs.stat(file.path);\n out.push({\n index: file.index,\n date: file.date,\n sizeBytes: stat.size,\n mtime: stat.mtime.toISOString(),\n });\n }\n return out;\n }\n\n /** Reads events from the Trace file at the given index, paginated by line (for loading large files in pages). */\n async readEvents(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n offset: number,\n limit: number,\n ): Promise<TraceEventsResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n return {\n events: messages.slice(offset, offset + limit),\n offset,\n limit,\n total: messages.length,\n };\n }\n\n /** Performance analysis: derived from a single Trace file. */\n async analyze(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<TraceAnalysisResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n\n const requests: RequestSpan[] = [];\n let openRequest: RequestSpan | null = null;\n const toolCalls: ToolCallSpan[] = [];\n const openToolCalls = new Map<string, ToolCallSpan>();\n let reconnectCount = 0;\n let compactionCount = 0;\n const usageTrend: UsageTrendPointInTrace[] = [];\n // Timeline (serial-duration estimation): Trace records completion times; model\n // messages are produced\n // serially (autoregressive decoding), so each segment's start = the previous\n // event's time (the request's first segment = request_begin); a tool's\n // approval/execution runs in parallel with model decoding, on its own lane;\n // prevSerialTs is cleared after request_end, and the next request_begin\n // restarts the count (which presumes all of the previous round's\n // tool_call_output have already come back).\n const modelSegments: TraceModelSegment[] = [];\n const toolSpans: TraceToolSpan[] = [];\n const openSpansById = new Map<string, TraceToolSpan>();\n let prevSerialTs: string | null = null;\n // Task grouping: one user turn contains multiple Request rounds (the Agent\n // loop sends another round each time it calls a tool); the turn ends once the\n // model produces only text with no further tool call. Consecutive Requests are\n // merged into one Task on this basis, and each Task gets its own independent\n // timeline — different Tasks can be far apart in time (the user is thinking or\n // has stepped away), and sharing one timeline would leave large gaps.\n // Compaction forms its own turn: both compaction_begin/compaction_end break a\n // continuation, so the compaction request becomes its own Task, and the\n // request that resumes after compaction starts yet another Task.\n let taskIndex = -1;\n let continuation = false; // The previous round's Request called a tool -> the next request_begin continues the same Task\n let sawToolCallThisRequest = false;\n // Compaction interval (compaction_begin..compaction_end): the compaction\n // request's request_begin/request_end and token_usage all fall inside it (see\n // core context-engine's summarize flow), which is used to exclude the\n // compaction request entirely from TPS — matching the same convention as\n // compactionActive in the Chat page's task-stats.\n let compactionActive = false;\n // Token / duration totals per Task (computed server-side over the whole file:\n // frontend events are fetched in pages, so summing them there would be\n // mismatched).\n const taskStats = new Map<number, TraceTaskStats>();\n const ensureTask = (ti: number): TraceTaskStats => {\n let t = taskStats.get(ti);\n if (t === undefined) {\n t = {\n taskIndex: ti,\n messageFrom: -1,\n messageTo: -1,\n startTs: \"\",\n endTs: \"\",\n tokens: { cacheRead: 0, cacheWrite: 0, output: 0 },\n llmMs: 0,\n };\n taskStats.set(ti, t);\n }\n return t;\n };\n\n /**\n * Which turn each message belongs to: **decided definitively in one\n * sequential pass**, not left for the frontend to guess by timestamp.\n *\n * Timestamp boundaries can't be pulled apart — the same millisecond can\n * contain \"the previous turn's last reply, compaction_begin, the compaction\n * prompt, and the next turn's request_begin\" all at once, so assigning by\n * time would inevitably misfile this turn's reply into the next turn.\n *\n * Rule (a turn = one user turn; `request_end` is the end of some Request\n * within a turn):\n * - The **starting marker** of a new turn: the main session's user Prompt\n * (outside compaction), or compaction_begin (compaction forms its own\n * turn). Messages after the marker and before that turn's first\n * `request_begin` (subsequent images from a multi-image send, the\n * compaction prompt) are always held pending, waiting for\n * `request_begin` to settle the new taskIndex before the whole span is\n * assigned at once — they belong to the **new** turn, not the tail of\n * the previous one.\n * - Other messages belong to the current taskIndex: tool output and\n * approval decisions arriving after request_end still belong to this\n * turn (they're the results of tools this turn's Request initiated).\n */\n const msgTask: number[] = new Array<number>(messages.length).fill(-1);\n /** The pending new turn's starting point (message index); settled once request_begin determines the taskIndex. */\n let pendingFrom: number | null = null;\n\n for (let mi = 0; mi < messages.length; mi++) {\n const msg = messages[mi]!;\n const p = msg.payload as Record<string, unknown> & { type?: string };\n // The timeline only looks at the main session (a Trace itself never contains origin messages; this is a defensive skip).\n const hasOrigin = msg.origin !== undefined && msg.origin.length > 0;\n\n // Starting marker of a new turn: the main session's user Prompt (outside\n // compaction) -> a new user turn; compaction_begin -> a compaction turn\n // (compaction forms its own turn). A single send can be \"text + multiple\n // images\" = multiple messages; only the **first** one counts (once\n // pendingFrom is set, it's not changed again), otherwise the turn's start\n // would shift to the last image.\n const startsUserTurn =\n !hasOrigin &&\n !compactionActive &&\n msg.type === \"model_msg\" &&\n ((p.type === \"text\" && p.role === \"user\") || p.type === \"image_url\");\n const startsCompactionTurn =\n !hasOrigin && msg.type === \"event_msg\" && p.type === \"compaction_begin\";\n if (startsUserTurn || startsCompactionTurn) {\n if (pendingFrom === null) pendingFrom = mi;\n // A user Prompt **always starts a new turn**: judging continuation solely\n // by \"did the previous turn call a tool\" isn't enough — if the previous\n // turn ended in timeout/malformed (given up after exhausting retries),\n // retryable would leave continuation at true, and this new message would\n // get merged into that failed turn, smearing the two turns' messages /\n // Tokens / TPS / duration together.\n if (startsUserTurn) continuation = false;\n }\n // A main-session message that isn't pending belongs to the current turn immediately (taskIndex < 0 = before the first request_begin, e.g. session_meta).\n if (!hasOrigin && pendingFrom === null) msgTask[mi] = taskIndex;\n\n if (msg.type === \"event_msg\") {\n if (p.type === \"request_begin\") {\n if (!hasOrigin) {\n prevSerialTs = msg.timestamp;\n if (!continuation) taskIndex++; // Not a continuation -> a new Task\n sawToolCallThisRequest = false;\n }\n // Settle taskIndex before opening the span: the span belongs directly to\n // the current Task. Nearest-neighbor pairing: if the previous begin was\n // never closed (process exited mid-run), the span is left open.\n openRequest = { beginTs: msg.timestamp, taskIndex };\n if (compactionActive) openRequest.compaction = true;\n requests.push(openRequest);\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n if (compactionActive) t.compaction = true; // This turn is a compaction turn\n // This turn's duration starts at the first request_begin. It doesn't\n // use the timestamp of the user Prompt / compaction summary or other\n // user text: `<context_summary>` is created during compaction but only\n // written to disk on the next run, so resuming the next day would\n // stretch the first turn out by a whole day for no reason; the Prompt\n // to request-dispatch gap is only ever milliseconds anyway.\n if (t.startTs === \"\") t.startTs = msg.timestamp;\n // The new turn's taskIndex is only settled here: the pending span\n // (user Prompt / multiple images / compaction prompt) is assigned in\n // full to **this** turn — they're the start of the new turn, not the\n // tail of the previous one.\n if (pendingFrom !== null) {\n for (let k = pendingFrom; k < mi; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n pendingFrom = null;\n }\n msgTask[mi] = taskIndex;\n }\n } else if (p.type === \"approval_decision\") {\n if (!hasOrigin && typeof p.tool_call_id === \"string\") {\n const span = openSpansById.get(p.tool_call_id);\n if (span && span.approvalTs === undefined) {\n span.approvalTs = msg.timestamp;\n if (typeof p.decision === \"string\") span.decision = p.decision;\n // Approval wait time is subtracted out of the LLM generation\n // duration: core does `await approve(tc)` inside the streaming loop,\n // so the entire manual wait sits between request_begin and\n // request_end (see RequestSpan.approvalWaitMs). Without subtracting\n // it, \"5s generation + 55s approval wait\" would show 100 tok/s as 8 tok/s.\n if (openRequest) {\n const wait = Date.parse(msg.timestamp) - Date.parse(span.callTs);\n if (Number.isFinite(wait) && wait > 0) {\n openRequest.approvalWaitMs = (openRequest.approvalWaitMs ?? 0) + wait;\n }\n }\n }\n }\n } else if (p.type === \"request_end\") {\n const status = typeof p.status === \"string\" ? p.status : undefined;\n // timeout/malformed is automatically reconnected by core within the same\n // run (context-engine's retry loop); the resent Request still belongs to\n // **the same user turn**: it must continue the turn, otherwise a single\n // timeout would split that turn's Tokens/duration/TPS across two Tasks.\n const retryable = status === \"timeout\" || status === \"malformed\";\n if (!hasOrigin) {\n prevSerialTs = null;\n continuation = sawToolCallThisRequest || retryable;\n }\n if (retryable) reconnectCount++;\n if (openRequest) {\n openRequest.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(openRequest.beginTs);\n if (Number.isFinite(dur)) {\n openRequest.durationMs = dur;\n openRequest.activeMs = Math.max(0, dur - (openRequest.approvalWaitMs ?? 0));\n }\n if (status !== undefined) openRequest.status = status;\n // TPS denominator: accumulated per the turn a Request belongs to. A\n // compaction request counts too — it belongs to **its own compaction\n // turn** (compaction forms its own turn), so it neither pollutes a\n // user turn's TPS, nor does the compaction turn fail to report its own\n // generation speed accurately. A failed retry's duration is counted as\n // well — it belongs to the same turn as the retry that eventually\n // succeeded, and \"how long this turn took to produce these tokens\"\n // should include the retries by definition.\n if (!hasOrigin && openRequest.activeMs !== undefined) {\n ensureTask(openRequest.taskIndex).llmMs += openRequest.activeMs;\n }\n openRequest = null;\n }\n } else if (p.type === \"compaction_begin\") {\n compactionCount++;\n // Compaction forms its own turn: otherwise, if the previous turn called\n // a tool, continuation would still be true and the compaction request\n // would get merged into the previous Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = true;\n }\n } else if (p.type === \"compaction_end\") {\n // Both ends of compaction break a continuation. This closing one can't\n // be skipped: if the compaction request itself exhausts its retries and\n // ends in timeout, the retryable check above would mark it as \"continued\",\n // and without clearing it here, the next user turn after compaction\n // would get merged into this compaction Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = false;\n }\n } else if (p.type === \"token_usage\") {\n const request = p.request as\n | { total?: number; cache_read?: number; cache_write?: number; output?: number }\n | undefined;\n const session = p.session as { total?: number } | undefined;\n usageTrend.push({\n ts: msg.timestamp,\n requestTotal: request?.total ?? 0,\n sessionTotal: session?.total ?? 0,\n });\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n // Cumulative usage for this turn (a running total): those tokens were\n // actually paid for, so the cost can't be dropped. `tokens.output` also\n // doubles as the numerator for output TPS — compaction's output\n // belongs to **its own compaction turn** (compaction forms its own\n // turn), so a user turn's TPS isn't polluted by it, while the\n // compaction turn can still accurately report \"how fast the summary\n // was generated\".\n t.tokens.cacheRead += request?.cache_read ?? 0;\n t.tokens.cacheWrite += request?.cache_write ?? 0;\n t.tokens.output += request?.output ?? 0;\n if (!compactionActive) {\n // The context snapshot only takes non-compaction Requests: tokens\n // consumed by compaction aren't the post-compaction context\n // footprint. A later write overwrites an earlier one -> this\n // naturally leaves behind the snapshot of the Task's **last**\n // non-compaction Request = the context footprint at the end of this\n // turn. Accumulating would be wrong: each Request's input carries\n // the entire history afresh (see TraceTaskStats).\n t.context = {\n cacheRead: request?.cache_read ?? 0,\n cacheWrite: request?.cache_write ?? 0,\n output: request?.output ?? 0,\n };\n }\n }\n }\n continue;\n }\n if (msg.type !== \"model_msg\") continue;\n // Model serial segments: assistant-side thinking/text/tool_call (a user input sent instantaneously occupies no segment).\n if (\n !hasOrigin &&\n prevSerialTs !== null &&\n (p.type === \"thinking\" ||\n p.type === \"tool_call\" ||\n (p.type === \"text\" && p.role === \"assistant\"))\n ) {\n const segment: TraceModelSegment = {\n kind: p.type === \"thinking\" ? \"thinking\" : p.type === \"tool_call\" ? \"tool_call\" : \"text\",\n startTs: prevSerialTs,\n endTs: msg.timestamp,\n taskIndex,\n };\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n segment.toolCallId = p.tool_call_id;\n if (typeof p.name === \"string\") segment.name = p.name;\n }\n modelSegments.push(segment);\n prevSerialTs = msg.timestamp;\n }\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n if (!hasOrigin) sawToolCallThisRequest = true; // This turn called a tool -> the next turn continues the same Task\n const callStop = typeof p.stop_reason === \"string\" ? p.stop_reason : undefined;\n const span: ToolCallSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n startTs: msg.timestamp,\n };\n if (callStop !== undefined) span.stopReason = callStop;\n openToolCalls.set(p.tool_call_id, span);\n toolCalls.push(span);\n // The timeline lane only accepts calls that \"will actually be executed\":\n // an interrupt-compensation tool_call (stop_reason other than completed)\n // never gets an approval/output, and putting it on a lane would render as\n // a phantom \"executing\" state spanning the whole timeline — so it's\n // skipped outright.\n if (!hasOrigin && (callStop === undefined || callStop === \"completed\")) {\n const timeline: TraceToolSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n callTs: msg.timestamp,\n taskIndex,\n };\n openSpansById.set(p.tool_call_id, timeline);\n toolSpans.push(timeline);\n }\n } else if (p.type === \"tool_call_output\" && typeof p.tool_call_id === \"string\") {\n const span = openToolCalls.get(p.tool_call_id);\n if (span && span.endTs === undefined) {\n span.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(span.startTs);\n if (Number.isFinite(dur)) span.durationMs = dur;\n if (typeof p.stop_reason === \"string\") span.stopReason = p.stop_reason;\n }\n const timeline = openSpansById.get(p.tool_call_id);\n if (timeline && timeline.outputTs === undefined) {\n timeline.outputTs = msg.timestamp;\n if (typeof p.stop_reason === \"string\") timeline.stopReason = p.stop_reason;\n }\n }\n }\n\n // A pending span that never got a request_begin (interrupted right after the\n // user sent it / the process exited): it's a turn that never got to run,\n // and forms its own turn — reattaching it to the previous turn would smear\n // two separate user sends together.\n if (pendingFrom !== null) {\n taskIndex++;\n for (let k = pendingFrom; k < messages.length; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n ensureTask(taskIndex);\n }\n\n // Each turn's message index range and end-of-turn time are always derived from\n // the per-message assignment done above (same source, so they never disagree\n // with each other). Messages before the first request_begin (session_meta)\n // have taskIndex -1 and are assigned to the first turn, otherwise they'd have\n // nowhere to sit on the page. The turn duration's **starting point** isn't\n // decided here — it was already settled at request_begin (duration only looks\n // at LLM requests; timestamps of the user Prompt / compaction summary or other\n // user text don't participate, see TraceTaskStats.startTs).\n const firstTask = [...taskStats.keys()].sort((a, b) => a - b)[0];\n for (let k = 0; k < messages.length; k++) {\n let ti = msgTask[k]!;\n if (ti < 0) {\n if (firstTask === undefined) continue;\n ti = firstTask;\n msgTask[k] = ti;\n }\n const t = ensureTask(ti);\n if (t.messageFrom < 0 || k < t.messageFrom) t.messageFrom = k;\n if (k > t.messageTo) t.messageTo = k;\n // session_meta is only **listed** in the first turn, and doesn't count\n // toward the end-of-turn time: it's metadata written when the session was\n // created, and its timestamp has nothing to do with this turn (it also gets\n // rewritten verbatim at the start of a new file after compaction splits the file).\n if (messages[k]!.type === \"session_meta\") continue;\n const ts = messages[k]!.timestamp;\n if (t.endTs === \"\" || ts > t.endTs) t.endTs = ts;\n }\n\n const tasks = [...taskStats.values()].sort((a, b) => a.taskIndex - b.taskIndex);\n // Total elapsed time = **the sum of each turn's duration**, matching exactly\n // the scope shown per-turn below (**including compaction turns** — their wall\n // clock time genuinely elapsed, each turn's card has its own duration, and the\n // overall total is their sum, so the numbers must add up). It is not \"last\n // message timestamp minus first message timestamp\": that would be the whole\n // file's wall-clock span, counting in the gaps **between** turns (the user\n // thinking, stepping out for coffee, coming back the next day) — none of which\n // is time the Agent spent working. A degenerate turn with no Request has an\n // empty startTs and counts as 0.\n // Note this uses a different convention from the Session's cumulative elapsed\n // time on the Chat page: that one only accumulates user turns (compaction\n // after a turn ends doesn't count toward the turn).\n const elapsedMs = tasks.reduce((sum, t) => {\n const span = Date.parse(t.endTs) - Date.parse(t.startTs);\n return sum + (Number.isFinite(span) ? Math.max(0, span) : 0);\n }, 0);\n return {\n elapsedMs,\n requests,\n tasks,\n toolCalls,\n modelSegments,\n toolSpans,\n reconnectCount,\n compactionCount,\n usageTrend,\n };\n }\n\n /** Level-by-level browsing (newest first): Agent -> date -> Session -> Trace files. */\n async agentTraces(projectId: string, agentId: string): Promise<AgentTracesResponse> {\n const dir = tracesDir(this.root, projectId, agentId);\n const dates = (await listDirs(dir)).sort().reverse();\n const out: AgentTracesResponse = { dates: [] };\n for (const date of dates) {\n const bySession = new Map<string, { index: number; sizeBytes: number }[]>();\n for (const file of await listFiles(path.join(dir, date))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match) continue;\n const sessionId = match[1]!;\n const stat = await fs.stat(path.join(dir, date, file));\n const files = bySession.get(sessionId) ?? [];\n files.push({ index: Number(match[2]), sizeBytes: stat.size });\n bySession.set(sessionId, files);\n }\n if (bySession.size === 0) continue;\n out.dates.push({\n date,\n // session_id embeds a timestamp, so reverse lexicographic order is reverse chronological order.\n sessions: [...bySession.entries()]\n .sort((a, b) => b[0].localeCompare(a[0]))\n .map(([sessionId, files]) => ({\n sessionId,\n files: files.sort((a, b) => a.index - b.index),\n })),\n });\n }\n return out;\n }\n\n private async readFileByIndex(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<OmniMessage[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const file = files.find((f) => f.index === index);\n if (!file) {\n throw new HttpError(\n 404,\n \"trace_not_found\",\n `该 Session 没有 index 为 ${index} 的 Trace 文件。`,\n );\n }\n return readTraceTolerant(file.path);\n }\n}\n","/**\n * Usage statistics query.\n *\n * Cost is **computed in real time**: usage_records only stores Tokens (pricing may\n * be added later), so at query time each Model's cost is converted using the\n * current Project's configured pricing — the repo returns raw Token totals broken\n * down by `(provider, model_id)` paired reference, and this service looks up each\n * reference's price once and folds it into cost / hasUncosted (if a Model has no\n * pricing, its consumption is excluded from cost and hasUncosted is flagged).\n * Summary cards (today / last 7 days / cumulative), grouped aggregation (date /\n * agent / model / session, with the session dimension supporting agentId drill-down\n * filtering), and a 30-day trend.\n * Server-side error statistics (error_records) ride along on the same response:\n * the statistics center fetches everything in one request, and filters are\n * naturally shared; unattributed errors (login failures, process crashes, and other\n * errors with no Project context) are visible only to admins, see the ErrorsRepo\n * file header.\n */\nimport type {\n UsageBucket,\n UsageErrors,\n UsageGroupBy,\n UsageGroupRow,\n UsageResponse,\n} from \"../api/types.js\";\nimport type { ErrorFilter, ErrorsRepo } from \"../db/repos/errors.js\";\nimport type {\n UsageRepo,\n UsageModelSums,\n UsageGroupModelSums,\n UsageFilter,\n} from \"../db/repos/usage.js\";\nimport { formatLocalDate, localDateMinusDays } from \"../internal/dates.js\";\n\n/** Number of most-recent entries kept in the error detail table. */\nconst ERROR_RECENT_N = 20;\n\n/** The three pricing buckets (usd_per_mtok convention), returned by the pricing lookup callback. */\nexport interface PricingRates {\n cacheRead: number;\n cacheWrite: number;\n output: number;\n}\n\nexport type PricingLookup = (\n projectId: string,\n provider: string,\n modelId: string,\n) => Promise<PricingRates | undefined>;\n\nexport interface UsageQuery {\n from?: string;\n to?: string;\n groupBy: UsageGroupBy;\n /** Top-level filter: view by Agent (also used for groupBy=session drill-down). */\n agentId?: string;\n /** Top-level filter: view by Model (paired with modelId; the dropdown always sends them as a pair). */\n provider?: string;\n modelId?: string;\n /** Whether to include unattributed errors: admin only (the route passes user.isAdmin), defaults to false. */\n includeGlobalErrors?: boolean;\n}\n\n/** Cost formula: sum of the three buckets, in USD per million Tokens. */\nfunction costOf(sums: UsageModelSums, rates: PricingRates): number {\n return (\n (sums.cacheRead * rates.cacheRead +\n sums.cacheWrite * rates.cacheWrite +\n sums.output * rates.output) /\n 1e6\n );\n}\n\n/** In-process Map key for a paired reference (\\0-separated, the same style as session-manager's agentKey; never persisted). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\nexport class UsageService {\n constructor(\n private readonly usage: UsageRepo,\n private readonly errors: ErrorsRepo,\n private readonly lookupPricing: PricingLookup,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n async query(projectId: string, q: UsageQuery): Promise<UsageResponse> {\n const today = formatLocalDate(this.now());\n // Top-level filter: agent + model (the cost center switches views by agent/model; the model filter is always sent as a pair).\n const base: UsageFilter = {};\n if (q.agentId !== undefined) base.agentId = q.agentId;\n if (q.provider !== undefined) base.provider = q.provider;\n if (q.modelId !== undefined) base.modelId = q.modelId;\n\n const win = (from?: string, to?: string): UsageFilter => ({\n ...base,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n });\n\n const todayRows = this.usage.bucketByModel(projectId, win(today, today));\n const last7dRows = this.usage.bucketByModel(projectId, win(localDateMinusDays(this.now(), 6)));\n const totalRows = this.usage.bucketByModel(projectId, win(q.from, q.to));\n const groupRows = this.usage.groupsByModel(projectId, q.groupBy, win(q.from, q.to));\n // Fixed 30-day window; affected by the agent/model filter.\n const trendFrom = localDateMinusDays(this.now(), 29);\n const trendRows = this.usage.groupsByModel(projectId, \"date\", win(trendFrom));\n // Agent call-count chart: not affected by the agent filter (shows all agents), but still affected by the date + model filter.\n const agentRows = this.usage.groupsByModel(projectId, \"agent\", {\n ...(q.provider !== undefined ? { provider: q.provider } : {}),\n ...(q.modelId !== undefined ? { modelId: q.modelId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Model success-rate chart: not affected by the model filter (shows all models), but still affected by the date + agent filter.\n const statusRows = this.usage.statusByModel(projectId, {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Error statistics: likewise not affected by the model filter (HTTP / process errors have no Model dimension), but still affected by the date + agent filter.\n const errorFilter: ErrorFilter = {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n // Unattributed errors are visible only to admins (regular members only see errors within their own Project, see the ErrorsRepo file header).\n ...(q.includeGlobalErrors === true ? { includeGlobal: true } : {}),\n };\n\n // Each paired reference that occurs is looked up for its current price only once.\n const rates = new Map<string, PricingRates | undefined>();\n const allRefs = new Map<string, { provider: string; modelId: string }>();\n for (const r of [...todayRows, ...last7dRows, ...totalRows, ...groupRows, ...trendRows]) {\n allRefs.set(refKey(r.provider, r.modelId), { provider: r.provider, modelId: r.modelId });\n }\n for (const [key, ref] of allRefs) {\n rates.set(key, await this.lookupPricing(projectId, ref.provider, ref.modelId));\n }\n\n const byAgentMap = new Map<string, { requests: number; total: number }>();\n for (const r of agentRows) {\n const acc = byAgentMap.get(r.key) ?? { requests: 0, total: 0 };\n acc.requests += r.requests;\n acc.total += r.total;\n byAgentMap.set(r.key, acc);\n }\n\n return {\n summary: {\n today: this.foldBucket(todayRows, rates),\n last7d: this.foldBucket(last7dRows, rates),\n total: this.foldBucket(totalRows, rates),\n },\n groupBy: q.groupBy,\n groups: this.foldGroups(groupRows, rates, q.groupBy),\n trend: this.foldTrend(trendRows, rates),\n byAgent: [...byAgentMap.entries()]\n .map(([agentId, v]) => ({ agentId, requests: v.requests, total: v.total }))\n .sort((a, b) => b.requests - a.requests),\n success: statusRows.sort((a, b) => b.total - a.total),\n errors: this.foldErrors(projectId, errorFilter),\n agentIds: this.usage.distinctAgentIds(projectId),\n models: this.usage.distinctModels(projectId),\n };\n }\n\n /** Error statistics: summary info (total / unexpected / most common error code) + the last N entries, all filtered by the selected range. */\n private foldErrors(projectId: string, f: ErrorFilter): UsageErrors {\n const { total, unexpected } = this.errors.summary(projectId, f);\n return {\n total,\n unexpected,\n topCode: this.errors.topCode(projectId, f),\n recent: this.errors.recent(projectId, f, ERROR_RECENT_N),\n };\n }\n\n private foldBucket(\n rows: UsageModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageBucket {\n let total = 0;\n let requests = 0;\n let cost: number | null = null;\n let hasUncosted = false;\n for (const r of rows) {\n total += r.total;\n requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) cost = (cost ?? 0) + costOf(r, rate);\n else hasUncosted = true;\n }\n return { total, requests, cost, hasUncosted };\n }\n\n private foldGroups(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n groupBy: UsageGroupBy,\n ): UsageGroupRow[] {\n // The model dimension folds by paired reference (a shared model_id name across providers is split into separate rows); other dimensions fold by their group key.\n const keyOf = (r: UsageGroupModelSums): string =>\n groupBy === \"model\" ? refKey(r.provider, r.key) : r.key;\n const byKey = new Map<string, UsageGroupRow>();\n for (const r of rows) {\n const acc = byKey.get(keyOf(r)) ?? {\n key: r.key,\n ...(groupBy === \"model\" ? { provider: r.provider } : {}),\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n requests: 0,\n cost: null as number | null,\n hasUncosted: false,\n };\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n acc.total += r.total;\n acc.requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n else acc.hasUncosted = true;\n byKey.set(keyOf(r), acc);\n }\n const out = [...byKey.values()];\n // The date dimension sorts by key descending (most recent first); other dimensions sort by total Token count descending.\n if (groupBy === \"date\") out.sort((a, b) => b.key.localeCompare(a.key));\n else out.sort((a, b) => b.total - a.total);\n return out;\n }\n\n private foldTrend(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageResponse[\"trend\"] {\n const byDate = new Map<\n string,\n { total: number; cacheRead: number; cacheWrite: number; output: number; cost: number | null }\n >();\n for (const r of rows) {\n const acc = byDate.get(r.key) ?? {\n total: 0,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n cost: null,\n };\n acc.total += r.total;\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n byDate.set(r.key, acc);\n }\n return [...byDate.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([date, v]) => ({\n date,\n total: v.total,\n cacheRead: v.cacheRead,\n cacheWrite: v.cacheWrite,\n output: v.output,\n cost: v.cost,\n }));\n }\n}\n","/**\n * Server runtime config (ServerConfig) — parsed from environment variables.\n *\n * The data root directory is shared with the SDK / CLI (`resolveRoot()`:\n * PENGUIN_HOME or ~/.penguin/data); the SQLite index database defaults to\n * `<root>/web.db` (overridable via PENGUIN_WEB_DB, tests use \":memory:\").\n * In production, the SPA is served statically once the frontend build output\n * directory (PENGUIN_WEB_DIST, the bundled web-dist/, or ../web/dist) is\n * detected to exist.\n * Docs: /docs/configuration § \"Environment variables\".\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveRoot } from \"@prismshadow/penguin-core\";\n\nexport interface ServerConfig {\n /** Local data root directory (shared with the SDK/CLI). */\n root: string;\n /** HTTP listen address and port (defaults to 127.0.0.1:7364, deliberately avoiding common ports like 3000/8080). */\n host: string;\n port: number;\n /** SQLite database path; \":memory:\" for test injection. */\n dbPath: string;\n /** Frontend static assets directory; whether it's enabled is decided by checking existence when the app is assembled. */\n webDist: string;\n /** Login session validity period (7 days). */\n authSessionTtlMs: number;\n /** Sliding renewal threshold: if the remaining validity is below this value when validation succeeds, it's renewed to the full TTL (renews under 6 days). */\n authSessionRenewMs: number;\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Default frontend build output directory, first match wins:\n * - `<this package>/web-dist`: npm package layout — the release workflow copies the built\n * web assets into the published package, so an `npm install` gets the Web UI too;\n * - `<this package>/../web/dist`: monorepo layout (resolves the same whether running\n * from src or dist), also the fallback when neither exists.\n */\nfunction defaultWebDist(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const bundled = path.resolve(here, \"..\", \"web-dist\");\n if (fs.existsSync(bundled)) return bundled;\n return path.resolve(here, \"..\", \"..\", \"web\", \"dist\");\n}\n\n/** Parses server config from environment variables (PORT / HOST / PENGUIN_HOME / PENGUIN_WEB_DIST / PENGUIN_WEB_DB). */\nexport function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const root = env.PENGUIN_HOME ?? resolveRoot();\n // An empty PORT string is treated as unset (the common `.env` case of an empty\n // `PORT=`): Number(\"\") === 0 would pass the range check and bind to a random\n // port; this matches the CLI's resolvePort convention.\n const port = Number(env.PORT || 7364);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`非法端口配置 PORT=${env.PORT}`);\n }\n return {\n root,\n host: env.HOST ?? \"127.0.0.1\",\n port,\n dbPath: env.PENGUIN_WEB_DB ?? path.join(root, \"web.db\"),\n webDist: env.PENGUIN_WEB_DIST ?? defaultWebDist(),\n authSessionTtlMs: 7 * DAY_MS,\n authSessionRenewMs: 6 * DAY_MS,\n };\n}\n"],"mappings":"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","names":["fs","path","Hono","mapRow","mapRow","mapRow","randomBytes","deps","randomBytes","deps","app","Hono","getCookie","deps","app","Hono","getCookie","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","createHash","Hono","isValidId","ms","path","path","isValidId","deps","app","Hono","createHash","Hono","deps","app","Hono","fs","path","Hono","app","Hono","deps","path","fs","fs","Hono","deps","app","Hono","fs","Hono","deps","app","Hono","fs","path","Hono","deps","app","Hono","path","fs","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","fs","path","Hono","fs","fs","fs","path","fs","path","deps","app","Hono","fs","path","fs","path","isSessionMeta","path","path","fs","deps","isSessionMeta","path","createHash","userText","deps","createHash","userText","deps","isEventMessage","isSessionMeta","isSessionMeta","originSessionId","isEventMessage","randomBytes","deps","fs","fs","path","parseToml","userText","fs","parseToml","path","userText","next","fs","config","path","fs","agentsDir","agentsMdPath","isValidId","loadAgentVault","scheduleDir","systemConfigPath","fs","agentsDir","isValidId","systemConfigPath","loadAgentVault","scheduleDir","agentsMdPath","fs","path","parseToml","parseYaml","asRecord","config","randomBytes","fs","path","parseYaml","agentDir","agentStateDir","agentStateVersion","systemConfigPath","path","fs","systemConfigPath","parseYaml","agentStateVersion","randomBytes","agentDir","agentStateDir","fs","path","fs","deps","path","readdir","createAgent","isSessionMeta","tracesDir","deps","tracesDir","path","createAgent","isSessionMeta","readdir","fs","path","agentsDir","readTraceTolerant","tracesDir","TRACE_FILE_RE","fs","tracesDir","path","agentsDir","readTraceTolerant","refKey","config","deps","app","Hono","fs","CONTENT_TYPES","path","fs","path"]}
|